Jump to content

Integrate Vue.js in a ProcessWire module


dotnetic
 Share

Recommended Posts

Hey folks,

@kongondo asked me some questions about how I integrated vue.js into ModulesManager2.

I was already planning to release a tutorial video of the integration process soon, but I don't have much time now as I am busy with customer work. So here is a quick roundup, which will be improved over time and become a full-blown tutorial. I hope to cover the basics and don't forget anything.

How did you implement the integration?
I created a new vue project via 

vue create .

inside my site/modules/mymodule folder

Do your assets still live under the Vue JS public folder?
I don't exactly know what you mean with assets. Are you speaking of images? I don't use images atm.

Where do your view files live, i.e. under your modules directory or in templates?
As I mentioned in point one, they are in the modules directory. Here is a screenshot of my directory:

image.png.407570b0e2b665821e67651d6a50bd50.png

As soon as I release the beta of ModulesManager2 you can go through the source code in github.

Where is your index.xxx file and how are you serving it?
vue-cli comes with a built in server and the index.html is automatically generated on-the-fly.

The command for running the server with HMR (hot media reload) resides in the package.json and is run via

npm run serve

This is the standalone server.

Anything else I should know (maybe .htaccess issues, etc)?
Create a vue.config.js in your custom module's root directory and add following parameters to it, to disable filename hashing:

const webpack = require("webpack");

module.exports = {
    runtimeCompiler: false,
    filenameHashing: false,
    pages: {
        main: {
            // entry for the page
            entry: 'src/main.js',
            // the source template
            template: 'public/index.html',
            // output as dist/index.html
            filename: 'index.html',
            // when using title option,
            // template title tag needs to be <title><%= htmlWebpackPlugin.options.title %></title>
            title: 'Index Page',
        }
    },
    configureWebpack: {
        plugins: [
            new webpack.ProvidePlugin({
                $: 'jquery',
                jQuery: 'jquery',
                'window.jQuery': 'jquery',
            })
        ]
    }
};

Edit: After creating the config run

npm run build

Then you can reference these files in your module like I did here:

$markup = '<div id="app"></div>';
$scriptPath = $this->config->urls->siteModules . $this->className;
$markup .= "<script src='$scriptPath/dist/js/chunk-vendors.js'></script>";
$markup .= "<script src='$scriptPath/dist/js/main.js'></script>";

I added the configureWebpack part to have access to the $ and jQuery objects inside of my vue files.

The install/uninstall overlay panel in MM2, is that something custom or a ProcessWire panel?
Standard ProcessWire panels

If it is a ProcessWire panel, did you have any difficulties implementing it into your Vue app?
ProcessWire's panel init happens before vue is initiated or rendered, so pw-panel links inside of vue are not catched.

To make pw-panel links inside of vue work, you have to defer (don't know if this is the correct term) the process to a body click event:

$(document).on("click", "#app .pw-panel", function (e, el) {
    e.preventDefault();
    let toggler = $(this);
    pwPanels.addPanel(toggler);
    toggler.click();
});

I hope this helps. If you have questions, please ask.

Edited by Jens Martsch - dotnetic
Added build step
  • Like 13
Link to comment
Share on other sites

Thanks @Jens Martsch - dotnetic! Excellent write-up.

23 minutes ago, Jens Martsch - dotnetic said:

Do your assets still live under the Vue JS public folder?
I don't exactly know what you mean with assets. Are you speaking of images? I don't use images atm.

You've sort of answered the question. What I meant was if you had images and such would they still live under /public?

23 minutes ago, Jens Martsch - dotnetic said:

Where is your index.xxx file and how are you serving it?
vue-cli comes with a built in server and the index.html is automatically generated on-the-fly.

Thanks. I thought the integration would have required to point to a different index.xxx (maybe an index.php) file inside ModulesManager2's folder. Good to know this is not the case.

23 minutes ago, Jens Martsch - dotnetic said:

If you have questions, please ask.

I suppose then that the API endpoint in ModulesManager2 is execute()?

 

23 minutes ago, Jens Martsch - dotnetic said:

I hope this helps.

A lot! Thanks.

  • Like 2
Link to comment
Share on other sites

  • 1 year later...

Hi! Wanted to know if anyone has a more elaborated guide on how to use Vuejs with Proceswirer for Process/Inputfield modules with hotreload and all the webpack nice thingies 🙂 so that for example, I can view the process/inputfield module in development in the context of the ProcessWire pages/routes.

  • Like 2
Link to comment
Share on other sites

  • 1 month later...
On 2/4/2021 at 10:45 PM, elabx said:

Wanted to know if anyone has a more elaborated guide on how to use Vuejs with Proceswirer for Process/Inputfield modules with hotreload and all the webpack nice thingies 🙂 so that for example, I can view the process/inputfield module in development in the context of the ProcessWire pages/routes.

I didn't, until earlier this week! I managed to get hotreload inside a ProcessWire Process Module that displays a Vue JS App in development mode, running under a different port from ProcessWire! I also threw in live reload in the mix for changes to my .module, .php files, etc. I'll do a writeup when I get some time. FYI, this only worked with vanilla Vue JS. I couldn't make it to work with Nuxt or Gridsome.

  • Thanks 1
Link to comment
Share on other sites

1 minute ago, kongondo said:

I didn't, until earlier this week! I managed to get hotreload inside a ProcessWire Process Module that displays a Vue JS App in development mode, running under a different port from ProcessWire! I also threw in live reload in the mix for changes to my .module, .php files, etc. I'll do a writeup when I get some time. FYI, this only worked with vanilla Vue JS. I couldn't make it to work with Nuxt or Gridsome.

Man I also figured this out, I hadn't had the time to come back and report!  It's all very rough but I basically load through the local server by vue-cli-service serve. I followed a Wordpress tutorial so I have no idea what the webpack config actually does and it must have something to do with the success, but it worked good enough to start working. Now I feel I've wasted my life without hot reload 🤣

Link to comment
Share on other sites

3 minutes ago, elabx said:

I followed a Wordpress tutorial

Same here! This one.

 

4 minutes ago, elabx said:

Now I feel I've wasted my life without hot reload 🤣

Hehe. Tell me about it!

  • Like 1
Link to comment
Share on other sites

  • 4 months later...

@kongondo
I would love to try vue for module development, but have trouble getting it to work. A tutorial would be highly appreciated! A vue module boilerplate would be nice 🙂 

I just started experimenting with vue, it looks very promising! Would it still be possible to run the module from within PW (when developing)? From what I understand vue uses it's own development server?

  • Like 1
Link to comment
Share on other sites

3 hours ago, jploch said:

A tutorial would be highly appreciated!

One is in the works....still in my head though...as I clear my in-tray first! (you know what I'm talking about 😉).

3 hours ago, jploch said:

Would it still be possible to run the module from within PW (when developing)? From what I understand vue uses it's own development server?

Yes and yes. If you are using Vue 2, there is a post somewhere in the forums (@elabx, help please, thanks) with a copy-amend-paste code from WP. That code will enable live reload in ProcessWire backend but view will still need to be running on some other port, e.g. 3000. However, you won't have to keep an eye on that as you will be viewing the changes in the ProcessWire backend. I have since adopted Vue 3 and Vite and the live reload in ProcessWire is as simple as this:

<?php
// e.g., in execute() if developing a Process Module
$out = "

// 3000 is the port vite is running at
$out .= '
  <script type="module" src="http://localhost:3000/@vite/client"></script>
  <script type="module" src="http://localhost:3000/src/main.js"></script>';
  
  //return $out; if in a method/function

Vite does not use Webpack. So, no (crazy) Webpack configs needed! The live reload just works!

Having said that, I highly recommend Alpine JS (as a Vue alternative, if you can get away with it) and HTMX (no Axios, no JSON!). With either, no build is required! You keep your DOM and work right inside ProcessWire. Future tutorials in the works, as well 😉. If you understand Vue then you already understand Alpine JS. If not, I still suggest you get to know the basics of Vue anyway.

Hope this helps.

Edited by kongondo
more info.
  • Like 2
Link to comment
Share on other sites

I managed to get it working. 
However for my module it would be nice to use PW to render some inputfields und use them together with vue. But what I have learned so far is that vue would be used to render the markup, so I would need to write the markup for the inputfields myself und put them in my js file? That would not be ideal, because the markup might change with new admin themes. Is there a way to use PWs API to create the inputfields and use vue to react to the user input? 

Link to comment
Share on other sites

3 minutes ago, jploch said:

However for my module it would be nice to use PW to render some inputfields und use them together with vue. But what I have learned so far is that vue would be used to render the markup, so I would need to write the markup for the inputfields myself und put them in my js file? 

Then Alpine JS it is 😁. Learn Vue, say thanks to the creators then say hello to Alpine JS. You will write its code right inside your ProcessWire inputfields, markup, whatever. Everything will be reactive. Make use of Inputfields->attr to assign Alpine properties. You might need the full attribute, e.g. 'x-on:change' instead of @change as ProcessWire might strip the latter out. Below is a quick example. I haven't checked it for errors.

Inside your ProcessWire code.

<?php

// get a ProcessWire inputfield wrapper
$wrapper =  new InputfieldWrapper();

// get an inputfield text to store a name
$field = $this->modules->get('InputfieldText');
// add some usual field values
$field->name = 'your_name';
// OR
//  $field->attr('name', 'your_name')
// etc...


// let's alpinejs-it!
$field->attr([
// MODEL THE VALUE DIRECTLY IN THE STORE
//   'x-model' => "\$store.mystore.person.name",
// OR HANDLE THE CHANGE YOURSELF
// @note: ProcessWire will strip out the '@' - so we use x-on:event instead
  'x-on:change' => "handleNameChange",// this is a method inside your Alpine data code
]);

// IF YOU NEED TO....
// set a wrapper-level class, e.g., as a show-if
// @note we escape some things here, e.g. the $
// could also use JavaScript template literals for the 'some_text', e.g. `some_text`
// here we add a hidden class to the wrapper depending on some store value
// we could have used x-show or x-if instead
$class = "{ hidden: \$store.mystore.some_text==\"some_text\"}";
$field->wrapAttr(
  'x-bind:class',
  $class,
);

$wrapper->add($field);

// initialise alpine js data
// 'persons' could also be an object here, but for more complex objects, better to assign to a function in your code like this
$out = "<div x-data='persons'>" . $wrapper->render() . "</div>";


return $out;

You JavaScript

// ALPINE (version 3+)
document.addEventListener("alpine:init", () => {
    Alpine.store("mystore", {
        // YOUR STORE FIELDS
		// for some results
		results: [],
		some_text: "some_text",
		is_modal_open: false,
		person: {},
	})
    Alpine.data("persons", () => ({
        handleNameChange(event) {
			this.changeCustomerName(event.target.value)
        },
        
        changeCustomerName(name) {
            this.$store.mystore.person.name = name
        }
    }))
})

Hope this helps.

  • Like 4
Link to comment
Share on other sites

If you go the Vue route, the easier way is to let it create the inputs but also assign usual ProcessWire classes to them, which then, would be picked up by ProcessWire styling when rendered. However, you will need the full house of markup including the wrappers somewhere wrapping the Vue-generated inputs. Otherwise, some ProcessWire styles might not be applied. Some label inputs might not appear, etc. Better to use Alpine if you can.

  • Like 1
Link to comment
Share on other sites

5 minutes ago, kongondo said:

Then Alpine JS it is 😁. Learn Vue, say thanks to the creators then say hello to Alpine JS. You will write its code right inside your ProcessWire inputfields, markup, whatever. Everything will be reactive.

Nice! Thanks for the suggestion. Alpine looks great. I will definitely check out vue und alpine. There is a lot of new stuff to learn, working mainly with jquery in the past. This might also be a little to late for my pagebuilder module Iam currently working on (almost done, it's written in jQuery und uses the change event on the inputfields). However refactoring that, might be a nice way to learn about these reactive js frameworks 🙂

  • Like 1
Link to comment
Share on other sites

21 hours ago, kongondo said:

I highly recommend Alpine JS (as a Vue alternative,

Depending on what you want to do, Alpine JS might not be a good alternative. A big drawdawn ist, that it doesn't use a virtual DOM and can get slow if you want to render many nodes, for example if you want to render many list entries with a pagination. At some point your browser will get slow and sluggish. But it is very good and easy in many other cases. There are also other good libraries out there, that have a similar purpose like vuejs/petite-vue: 5kb subset of Vue optimized for progressive enhancement (github.com) (also does not use a virtual DOM, so the same drawback as Alpine) which shares the same template syntax and reactivity mental model with standard Vue or there is riot.js.

Petite-vue might be the right choice for you @jploch.

Vite is great and really fast and I am happy to have it as a replacement to webpack and I use it since it appeared in most projects (well actually it is not really a replacement for webpack, because it is NOT a bundler, but it serves a similar purpose).

 

  • Like 3
Link to comment
Share on other sites

2 hours ago, dotnetic said:

an get slow if you want to render many nodes, for example if you want to render many list entries with a pagination. At some point your browser will get slow and sluggish.

Do you have a reference or is it from personal experience? Have you seen this discussion and/or x-ignore? I don't want to derail the topic in this thread though, so we might have to hive off this talk. 🙂.

Edit: A reference to a similar issue to @dotnetic's

Edited by kongondo
update
Link to comment
Share on other sites

@kongondo I have experimented with Alpine.js and setting the attributes on the inpufields via PW api like you showed, and it works nicely! It's really a game changer compared to doing it in nativ js or jQuery! My code is now roghly 1/6 the size it was before and it's quite intuitive to use as well. Thanks for the hint! I highly recomend others to try this as well. Btw. it also works together with jQuery without a problem, if you want to keep that as part of your workflow.

EDIT: Still having some trouble figuring things out with Alpine. For example I have JSON data saved inside a PW field. Now I want to use that data to pupulate my inputfields. Looking at your example, I guess I have to use alpine.store for that. How would that work?

  • Like 1
Link to comment
Share on other sites

3 hours ago, jploch said:

I have experimented with Alpine.js and setting the attributes on the inpufields via PW api like you showed, and it works nicely!

Glad you got it sorted...😀

Until you didn't...😉

3 hours ago, jploch said:

EDIT: Still having some trouble figuring things out with Alpine. For example I have JSON data saved inside a PW field. Now I want to use that data to pupulate my inputfields. Looking at your example, I guess I have to use alpine.store for that. How would that work?

Please open a separate thread, maybe under dev talk as this is about Alpine. We'll then continue this conversation there, thanks.

  • Like 2
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...