Jump to content
Sign in to follow this  
louisstephens

Incorporating vue with Processwire

Recommended Posts

So I have been diving into vue.js lately, and I am really impressed. I thought it could be perfect for an internal dashboard using PageQuerry boss to retrieve the data. However, I do have a few questions that someone might be able to guide me with before I fully commit. 

1. Is it even possible to bootstrap pw into a vue app?

2. How would it be possible to handle user registration/logins without actually having to mirror the users on pw in another service? 

3. I know I could easily use an iframe in a modal to handle page editing (as I have done it inside of pw before), but would this be the best solution?

Share this post


Link to post
Share on other sites

Just very quickly...

  1. Not sure, but why would you want to? It's enough if you sit down and think about what kind of data you'll gonna need from PW. Then create appropriate JSON files that deliver raw data to your Vue.js app. You don't even need something like PageQueryBoss for that. Send a request from Vue to PW, do your usual $pages->find() or whatever, create an array, do json_encode(), set appropriate JSON-headers, and that's it.
  2. Frankly, haven't tried that. But you don't have to "mirror" PW users in your Vue.js app. Just send requests with credentials to PW, and return true/false and maybe add an HTTP header token for logins. For registrations, it's going to be a little more coding, but with PW's API, it shouldn't be too hard.
  3. Do you want to render/show PW's native page-edit view within that modal, or do you plan to re-create the whole page-edit functionality in Vue? I'd steer away from the latter, cause you'd be re-creating the proverbial wheel.

 

  • Like 1

Share this post


Link to post
Share on other sites

Thanks dragan. In my head I just assumed that I would need to bootstrap it. I tried doing a simple ajax request like {{info}} is in the actual markup for the controller:

import axios from 'axios';

export default {
  name: 'Api',
  data () {
    return {
      info: null
    }
  },
  mounted () {
	axios
		.get('http://localhost:8888/development/sites/test/ajax-actions/test-api')
		.then(response => (this.info = response))
  }
}

And in my processwire ajax-actions.php  file:

<?php
else if ($config->ajax && $input->urlSegment1 == 'test-api') {
    $searchResults = $page->find("template=basic");
    $json = array();
    foreach($searchResults as $search){
        $json[] = array(
          'title' => $seach->title,
         );
    }
    echo json_encode($json);
}
?>

 

I guess I have two issues with this setup. 

1. In my console on my vue app, I get No 'Access-Control-Allow-Origin' header is present on the requested resource

2. And secondly, data that is output seems to be just "

"{ "data": "", "status": 200, "statusText": "OK", "headers": { "pragma": "no-cache", "content-type": "text/html; charset=utf-8", "cache-control": "no-store, no-cache, must-revalidate", "expires": "Thu, 19 Nov 1981 08:52:00 GMT" }, "config": { "transformRequest": {}, "transformResponse": {}, "timeout": 0, "xsrfCookieName": "XSRF-TOKEN", "xsrfHeaderName": "X-XSRF-TOKEN", "maxContentLength": -1, "headers": { "Accept": "application/json, text/plain, */*" }, "method": "get", "url": "http://localhost:8888/development/sites/test/ajax-actions/test-api" }, "request": {} }

 

What have I missed? Sorry, I am still trying to figure this all out.

Share this post


Link to post
Share on other sites

You have several typos in your code. 

  $searchResults = $pages->find("template=product")->explode(function($item, $i) {
      return [
            'title' =>  $item . ' - ' . $i
          ];
  });

 d(wireEncodeJSON($searchResults));

 

  • Like 1

Share this post


Link to post
Share on other sites

Thanks zeka, I was playing around with your suggestion, but if I look at the console, "data" is completely empty. I can out put the "json" by just encoding it, but it isnt passing anything actual to the app. Ill keep searching.

{ "data": "", "status": 200, "statusText": "OK", "headers": { "pragma": "no-cache", "content-type": "text/html; charset=utf-8", "cache-control": "no-store, no-cache, must-revalidate", "expires": "Thu, 19 Nov 1981 08:52:00 GMT" }, "config": { "transformRequest": {}, "transformResponse": {}, "timeout": 0, "xsrfCookieName": "XSRF-TOKEN", "xsrfHeaderName": "X-XSRF-TOKEN", "maxContentLength": -1, "headers": { "Accept": "application/json, text/plain, */*" }, "method": "get", "url": "http://1ocalhost:8888/development/sites/test/ajax-actions/test-api/" }, "request": {} }

 

Share this post


Link to post
Share on other sites

A few things...

With the axios call, try querying the relative and not the absolute url. Also, take note of the trailing slash on the url, sometimes that can trip you up depending on your template settings. Finally, you probably don't need to set the entire response to your Vue data object, with axios you are looking for response.data. 

axios.get('/ajax-actions/test-api/).then(response => (this.info = response.data));

If you need to keep the full absolute url, or if you are calling the api page from a different url, then look into CORS. Setting the header below on your template should be enough, depends on your setup (eg see this topic for issues with CORS when using procache).

<php
  header('Content-Type: application/json,charset=utf-8');
  header("access-control-allow-origin: *");
  //...
  ?>

 

  • Like 2

Share this post


Link to post
Share on other sites

Thanks @mke and everyone else for the help. I got the "content-type" in the headers to return as application/json now. It's odd to me that I can call response.headers and return all the header information for the call, but the data is just blank. I looked at the json on the the actual page and it is being returned as:

[{"title":"Development","summary":"Epic cheeseburgers come in all kinds of manifestations, but we want them in and around our mouth no matter what. Slide those smashed patties with the gently caramelized meat fat between a toasted brioche bun and pass it over. You fall in love with the cheeseburger itself but the journey ain\u2019t half bad either.","body":"
They\u2019re the childhood friend that knows your highest highs and lowest lows. They\u2019ve been with you through thick and thin and they\u2019re the best at keeping secrets. Whether it\u2019s dressed up or informal, cheeseburgers have your back.<\/p>\n\n

Sometimes we lose sight of what really matters in life. There\u2019s something to be said for a gourmet brie and truffle burger paired with parmesan frites, but don\u2019t let that make you forget about the ol\u2019 faithful with American cheddar and a squishy bun. Lettuce remind you that cheeseburgers come in all forms - bun intended.<\/p>\n\n

Pop quiz: what\u2019s the greatest thing to happen to your mind, body, and soul in recent history? A cheeseburger, obviously. Cheeseburgers know that what you want can also be what you need.<\/p>"}]

(dont mind the placeholder text. Cheeseburger Ipsum.)

I do have processwire in a subfolder, and my vue app is in a folder outside (just so I did not get to turned around). I also checked to make sure my api call began and ended with a slash as suggested but it doesnt seem to make a difference as of yet. My get response appears to be "200" coming from the vue app as well, so now I am just really baffled.

** Edit **

I did just find if I take my foreach statement:

<?php
$myPages = $pages->find('template=basic-page');
    $data = $myPages->explode(['title', 'summary', 'body']);
    echo wireEncodeJSON($data);
?>

and remove it outside of 

if ($config->ajax && $input->urlSegment1 == 'test-api') {

}

then the data attribute is properly filled out.

Share this post


Link to post
Share on other sites

Hey glad you figured out, just realised I'd misunderstood previous post.

 

  • Like 1

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By thomasaull
      I recently started to build Vue SPAs with ProcessWire as the backend, connected with a REST API. Thanks to code and the help of @LostKobrakai (How to use FastRoute with ProcessWire) and @clsource (REST-Helper) I got it up and running pretty quickly and now have put all of it in a site profile for others to use. It includes the REST API with routing for different endpoints, JWT Auth and a simple Vue SPA which shows the process of logging in a user (nevertheless, you don't have to use the Vue part, the API will work on it's own).
      Check it out here: https://github.com/thomasaull/RestApiProfile
      I'm pretty sure, it's not the perfect or most sophisticsted solution, but it gets the job done for me…
      Feedback or Improvements are very welcome
      Update: This site profile is a module now: https://github.com/thomasaull/RestApi
    • By Noel Boss
      ProcessWire & Vue.js — a Lovestory
      Introducing the all new ICF Conference Website
        The new ICF Conference Page — Fearless
      » What would happen if we were equipped to fearlessly face the daily challenges and live a life without fear? «
      This question is at the core of our next ICF Conference in 2019 in Zurich. Its also the question we set out to answer in terms of developing the new website; the all new ICF Conference website is our most advanced website in terms of technology, designed to take advantage of the latest web-technologies.
      Its a brand new design powered by a lean setup, using ProcessWire for easy content management and a slick frontend based on Vue.js, Quasar and a heavily customized Uikit theme.
        Technology-stack — From backend to frontend, technologies that are fun, easy and fast to develop with We built on the ICF Ladieslounge website as a solid foundation and took our learnings from building our last Conference Booklet PWA (Progressive Web App) and applied it to the new website.
      Some highlights of the new ICF Conference website:
      Completely decoupled backend and frontend Custom design based on Uikit frontend framework Changing of languages happens instantly, no page-reload required Easy content updates thanks to ProcessWire All data is transferred using a single request returning custom JSON



      » Continue reading on Medium
      And please don't forget to clap and share: 

       
    • By Noel Boss
      Page Query Boss
      Build complex nested queries containing multiple fields and pages and return an array or JSON. This is useful to fetch data for SPA and PWA.
      You can use the Module to transform a ProcessWire Page or PageArray – even RepeaterMatrixPageArrays – into an array or JSON. Queries can be nested and contain closures as callback functions. Some field-types are transformed automatically, like Pageimages or MapMarker.
      Installation
      Via ProcessWire Backend
      It is recommended to install the Module via the ProcessWire admin "Modules" > "Site" > "Add New" > "Add Module from Directory" using the PageQueryBoss class name.
      Manually
      Download the files from Github or the ProcessWire repository: https://modules.processwire.com/modules/page-query-builder/
      Copy all of the files for this module into /site/modules/PageQueryBoss/ Go to “Modules > Refresh” in your admin, and then click “install” for the this module. Module Methods
      There are two main methods:
      Return query as JSON
      $page->pageQueryJson($query); Return query as Array
      $page->pageQueryArray($query); Building the query
      The query can contain key and value pairs, or only keys. It can be nested and 
      contain closures for dynamic values. To illustrate a short example:
      // simple query: $query = [ 'height', 'floors', ]; $pages->find('template=skyscraper')->pageQueryJson($query); Queries can be nested, contain page names, template names or contain functions and ProcessWire selectors:
      // simple query: $query = [ 'height', 'floors', 'images', // < some fileds contain default sub-queries to return data 'files' => [ // but you can also overrdide these defaults: 'filename' 'ext', 'url', ], // Assuming there are child pages with the architec template, or a // field name with a page relation to architects 'architect' => [ // sub-query 'name', 'email' ], // queries can contain closure functions that return dynamic content 'querytime' => function($parent){ return "Query for $parent->title was built ".time(); } ]; $pages->find('template=skyscraper')->pageQueryJson($query); Keys:
      A single fieldname; height or floors or architects 
      The Module can handle the following fields:
      Strings, Dates, Integer… any default one-dimensional value Page references Pageimages Pagefiles PageArray MapMarker FieldtypeFunctional A template name; skyscraper or city
      Name of a child page (page.child.name=pagename); my-page-name A ProcessWire selector; template=building, floors>=25
      A new name for the returned index passed by a # delimiter:
      // the field skyscraper will be renamed to "building": $query = ["skyscraper`#building`"]  
      Key value pars:
      Any of the keys above (1-5) with an new nested sub-query array:
      $query = [ 'skyscraper' => [ 'height', 'floors' ], 'architect' => [ 'title', 'email' ], ]  
      A named key and a closure function to process and return a query. The closure gets the parent object as argument:
      $query = [ 'architecs' => function($parent) { $architects = $parent->find('template=architect'); return $architects->arrayQuery(['name', 'email']); // or return $architects->explode('name, email'); } ] Real life example:
      $query = [ 'title', 'subtitle', // naming the key invitation 'template=Invitation, limit=1#invitation' => [ 'title', 'subtitle', 'body', ], // returns global speakers and local ones... 'speakers' => function($page){ $speakers = $page->speaker_relation; $speakers = $speakers->prepend(wire('pages')->find('template=Speaker, global=1, sort=-id')); // build a query of the speakers with return $speakers->arrayQuery([ 'title#name', // rename title field to name 'subtitle#ministry', // rename subtitle field to ministry 'links' => [ 'linklabel#label', // rename linklabel field to minlabelistry 'link' ], ]); }, 'Program' => [ // Child Pages with template=Program 'title', 'summary', 'start' => function($parent){ // calculate the startdate from timetables return $parent->children->first->date; }, 'end' => function($parent){ // calculate the endate from timetables return $parent->children->last->date; }, 'Timetable' => [ 'date', // date 'timetable#entry'=> [ 'time#start', // time 'time_until#end', // time 'subtitle#description', // entry title ], ], ], // ProcessWire selector, selecting children > name result "location" 'template=Location, limit=1#location' => [ 'title#city', // summary title field to city 'body', 'country', 'venue', 'summary#address', // rename summary field to address 'link#tickets', // rename ticket link 'map', // Mapmarker field, automatically transformed 'images', 'infos#categories' => [ // repeater matrix! > rename to categories 'title#name', // rename title field to name 'entries' => [ // nested repeater matrix! 'title', 'body' ] ], ], ]; if ($input->urlSegment1 === 'json') { header('Content-type: application/json'); echo $page->pageQueryJson($query); exit(); } Module default settings
      The modules settings are public. They can be directly modified, for example:
      $modules->get('PageQueryBoss')->debug = true; $modules->get('PageQueryBoss')->defaults = []; // reset all defaults Default queries for fields:
      Some field-types or templates come with default selectors, like Pageimages etc. These are the default queries:
      // Access and modify default queries: $modules->get('PageQueryBoss')->defaults['queries'] … public $defaults = [ 'queries' => [ 'Pageimages' => [ 'basename', 'url', 'httpUrl', 'description', 'ext', 'focus', ], 'Pagefiles' => [ 'basename', 'url', 'httpUrl', 'description', 'ext', 'filesize', 'filesizeStr', 'hash', ], 'MapMarker' => [ 'lat', 'lng', 'zoom', 'address', ], 'User' => [ 'name', 'email', ], ], ]; These defaults will only be used if there is no nested sub-query for the respective type. If you query a field with complex data and do not provide a sub-query, it will be transformed accordingly:
      $page->pageQueryArry(['images']); // returns something like this 'images' => [ 'basename', 'url', 'httpUrl', 'description', 'ext', 'focus'=> [ 'top', 'left', 'zoom', 'default', 'str', ] ]; You can always provide your own sub-query, so the defaults will not be used:
      $page->pageQueryArry([ 'images' => [ 'filename', 'description' ], ]); Overriding default queries:
      You can also override the defaults, for example
      $modules->get('PageQueryBoss')->defaults['queries']['Pageimages'] = [ 'basename', 'url', 'description', ]; Index of nested elements
      The index for nested elements can be adjusted. This is also done with defaults. There are 3 possibilities:
      Nested by name (default) Nested by ID Nested by numerical index Named index (default):
      This is the default setting. If you have a field that contains sub-items, the name will be the key in the results:
      // example $pagesByName = [ 'page-1-name' => [ 'title' => "Page one title", 'name' => 'page-1-name', ], 'page-2-name' => [ 'title' => "Page two title", 'name' => 'page-2-name', ] ] ID based index:
      If an object is listed in $defaults['index-id'] the id will be the key in the results. Currently, no items are listed as defaults for id-based index:
      // Set pages to get ID based index: $modules->get('PageQueryBoss')->defaults['index-id']['Page']; // Example return array: $pagesById = [ 123 => [ 'title' => "Page one title", 'name' => 123, ], 124 => [ 'title' => "Page two title", 'name' => 124, ] ] Number based index
      By default, a couple of fields are transformed automatically to contain numbered indexes:
      // objects or template names that should use numerical indexes for children instead of names $defaults['index-n'] => [ 'Pageimage', 'Pagefile', 'RepeaterMatrixPage', ]; // example $images = [ 0 => [ 'filename' => "image1.jpg", ], 1 => [ 'filename' => "image2.jpg", ] ] Tipp: When you remove the key 'Pageimage' from $defaults['index-n'], the index will again be name-based.
       
      Help-fill closures & tipps:
      These are few helpfill closure functions you might want to use or could help as a
      starting point for your own (let me know if you have your own):

      Get an overview of languages:
          $query = ['languages' => function($page){         $ar = [];         $l=0;         foreach (wire('languages') as $language) {             // build the json url with segment 1             $ar[$l]['url']= $page->localHttpUrl($language).wire('input')->urlSegment1;             $ar[$l]['name'] = $language->name == 'default' ? 'en' : $language->name;             $ar[$l]['title'] = $language->getLanguageValue($language, 'title');             $ar[$l]['active'] = $language->id == wire('user')->language->id;             $l++;         }         return $ar;     }]; Get county info from ContinentsAndCountries Module
      Using the [ContinentsAndCountries Module](https://modules.processwire.com/modules/continents-and-countries/) you can extract iso
      code and names for countries:
          $query = ['country' => function($page){         $c = wire('modules')->get('ContinentsAndCountries')->findBy('countries', array('name', 'iso', 'code'),['code' =>$page->country]);         return count($c) ? (array) $c[count($c)-1] : null;     }]; Custom strings from a RepeaterTable for interface
      Using a RepeaterMatrix you can create template string for your frontend. This is
      usefull for buttons, labels etc. The following code uses a repeater with the
      name `strings` has a `key` and a `body` field, the returned array contains the `key` field as,
      you guess, keys and the `body` field as values:
          // build custom translations     $query = ['strings' => function($page){         return array_column($page->get('strings')->each(['key', 'body']), 'body', 'key');     }]; Multilanguage with default language fallback
      Using the following setup you can handle multilanguage and return your default
      language if the requested language does not exist. The url is composed like so:
      `page/path/{language}/{content-type}` for example: `api/icf/zurich/conference/2019/de/json`
       
          // get contenttype and language (or default language if not exists)     $lang = wire('languages')->get($input->urlSegment1);     if(!$lang instanceof Nullpage){         $user->language = $lang;     } else {         $lang = $user->language;     }     // contenttype segment 2 or 1 if language not present     $contenttype = $input->urlSegment2 ? $input->urlSegment2 : $input->urlSegment1;     if ($contenttype === 'json') {         header('Content-type: application/json');         echo $page->pageQueryJson($query);         exit();     } Debug
      The module respects wire('config')->debug. It integrates with TracyDebug. You can override it like so:
      // turns on debug output no mather what: $modules->get('PageQueryBoss')->debug = true; Todos
      Make defaults configurable via Backend. How could that be done in style with the default queries?
      Module in alpha Stage: Subject to change
      This module is in alpha stage … Query behaviour (especially selecting child-templates, renaming, naming etc)  could change
    • By heldercervantes
      Hi guys / gals.
      Rimaa deals with logistics and moving products around. They needed an interface for their clients to check the status on their orders and that's where I came in.
      Not much you can see just by visiting the site, because this is actually a private mobile oriented web-app. But see some screenshots below.
      This project features some cool things under the hood:
      First, the content is managed from a Google Spreadsheet. PW connects to the spreadsheet on an interval (that can be configured), parses the sheet and updates the orders list. So the various people that share that spreadsheet and keep it updated for internal control, are also maintaining this platform without even knowing it.
        PW users templates was modified to allow for this platform's different roles: Client : only sees his orders Agent : has a set of clients he can overview Super-agent : sees all clients
        Updated orders visual cue: Comparing the current version of an order (by $page->modified) with last viewed version's date (stored in an repeater on each client), the app turns on a visual cue that the order has been updated since it was last seen. Viewing an order is an accordion-style CSS toggle, so at that point an Ajax call tells PW to update the last version viewed by the current user. All in all, a very little project that once more shows the flexibility we get from this awesome API. Being able to modify users to have the fields we need to get something like this working is a beautiful thing.

       
    • By microcipcip
      Hi, I have created a site profile that shows how to integrate ProcessWire 3.0 with Vue 2.0. See repository here.
       
      How this site profile works
      This ProcessWire site profile is loosely based on the REST API tutorial by @gebeer. Here are the most important steps to reproduce it:
       
      Admin settings
      Create an api template with default settings and just the title field assigned to it. Refer to @gebeer tutorial for further details
      Create a pages and nav templates with just the title field, for both template tick “Allow URL Segments” in the “URLs” tab (see attachment)
      Create a home template, this is going to be the single php file that will load your Vue SPA. Assign this template to the root of your website
      Any other template you create should have the “Alternate Template Filename” field in the “Files” tab set as home (see attachment), in this way if a user enter the website from any url that is not the root, ProcessWire will always redirect to the home template, Vue router will handle the url and call the right data through the REST API
      Under the root, create an api page and assign the api template to it (you can set “hidden” to this page so doesn't show up in the menu)
      Under the api page, create the pages nav and pages (see attachment), and assign the templates nav and pages to them. Now you have the www.sitename.com/api/pages and www.sitename.com/api/nav urls that you can use to fetch the JSON data
       
      PHP template setup
      In the templates folder, create home.php file and leave it empty, the HTML will be generated by webpack
      Now create pages.php and nav.php files. On these files is where we return the JSON data, based on the right url segment. Again, refer to @gebeer tutorial for further details on this matter. Note that I wrote a PageFields class that I use on these templates to fetch ProcessWire fields. The fields that are supported are text, textarea, repeater, img. Other fields may work but I haven't tested them. See the REST API setup for further details about how to use the PageFields class

      REST API setup
      You can decide what fields are included and what fields are excluded by passing a configuration array to the PageFields class. You can find here a list of the available configuration settings. See examples below.
       
      Show only selected core fields: $pageFields = new PageFields($p, [ 'fld_core_included' => ['url', 'httpUrl', 'template'] ]);  
      Show no global fields, and only selected custom fields:
      $pageFields = new PageFields($p, [ 'fld_core_included' => [], 'fld_include_all' => false, 'fld_included' => ['title', 'gallery'], ]);  
      On a gallery image field, hide breakpoint listing and show only httpUrl field:
      $pageFields = new PageFields($p, [ 'img_fld_overrides' => [ 'gallery' => [ 'fields' => ['httpUrl'], 'bp_list' => false ] ], ]);  
      Webpack setup
      The most important file of all Webpack setup is config/index.js. On line 33 you need to provide your domain name so that Webpack can proxy the ProcessWire REST API to the Webpack dev server. Without this you wouldn't be able to take advandage of the Webpack hot module replacement feature which allows you to reload a vue module without refreshing the page, it also allows you to keep the state of the app.
       
      Notes
      My REST API may have bugs, this is just an example of an integration with ProcessWire, I suggest you either build your own REST API or use the awesome GraphQL module by @Nurguly Ashyrov.

      Todo
      Replace REST API with the GraphQL module. This requires vue-apollo, the Apollo/GraphQL integration with Vue, and vue-supply for integration with Vuex.
       
       
×
×
  • Create New...