Introduction
The vue-plugin-1 is a wordpress plugin template which uses the Vue JS framwork for generating the plugin admin pages in the backend. Another library Vite is used as the package bundler for development and production build.
This plugin generates one admin page and one admin sub-page. The sub-page also contains 2 tabs.
This plugin makes use of the WP REST API to add/change a record in the wp_options table in the database.
To avoid the ownerships and permissions complexity in Linux, the development is on the local PC using Laragon setup on the SSD drive named “KingstonSSD“.
- Site url : http://hello.test
- Site Dashboard URL: http://hello.test/wp-admin
- A/C:
web_devhk / password default
- A/C:
Create PHP development environment
The envirnoment has thees files involved:
vue-plugin-1.php- constructor:
- call admin_constants() in class DefineConstants for the plugin templates.
- run the built-in
register_activation_hook()to point to the methodactivate()method which will be run during plugin activation. - run the built-in
register_deactivation_hook()to point to the methoddeactivation()which will be run during plugin deactivation. Currently deactivation is doing nothing. - at the
plugins_loadedhook, run theinit_others()method. Theinit_others()method is to instantiate all required classes one by one. - the static function
init()creates a singletone instance of this class. This class is instantiated by the commandVue_plugin_1::init()at the last line of the file.
- constructor:
uninstall.php- WP executes this file when the plugin is being uninstalled.
- remove the option “
IvrVP1” from thewp_optionstable.
composer.json- define autoload of class files based on namespaces.
- See “PHP – Autoload classes and non-classes files using Composer“.
- Frequently used commands:
composer init–> create the filecomposer.jsoncomposer dump-autoload–> create/update thevendorfolder and the fileautoload.phpinside the folder.
/includes/Backend/Activate.php- WP executes this class file when the plugin is being activated.
- check if the option “
IvrVP1” exists, if not add this option to thewp_optionstable. - the subdirectory first character should be capitalised to conform to PSR-4.
/includes/Backend/CreateMenuPages.php- In the constructor
- hook
admin_menuis used to create the menu pages which covers the mount-point for the Vue app, the slugs for the menu pages, etc. - the slugs contain the ” #” sign in order to load different views for a single page application (SPA).
- hook
admin_enqueue_scriptsis to register and enqueue the required scripts and style files. The URLs are pointed to the files generated by Vue in the/vueapp/dist/assetsdirectory. Thewp_locatlze_scriptfunction is used to pass PHP variables in an array to Vue. - filter
script_loader_tagis used to addtype="module"to the<script>tag of the above script. This is required to make the script acts as a javascript module.
- hook
- In the constructor
/includes/Api/ApiEndPoints.php- this class extends the
WP_REST_Controllerclass and defines the REST API endpoints. - In the constructor:
- set the custom route with self-defined api namespace and base.
- at the
rest_api_inithook, runs theregister_routesmethod.
- the REST API namespace is “
ivrvp1/v1“. - the REST API base is “
settings“. - the complete API custom route is “http://hello.test/wp-json/ivrvp1/v1/settings”.
- this route has 2 endpoints – (1) with method “
WP_REST_Server::READABLE” which is equivalent toGET. (2) with method “WP_REST_Server::CREATABLE” which is equivalent toPOST. - one of the
register_rest_routearguments, thePermissoin Callbackis run only after remote authentication is valid. The remote authentication can be done by passing a nonce to the server in the request header. The nonce is generated inCreateMenuPages.phpand passed to JS via array$jsDatausing thewp_localize_script()function.
- this class extends the
Create Vue JS development environment
- activate the plugin.
- create a vue app under the plugin root directory as per Reference 1.
- under the plugin root directory, run
npm create vite@latest vueapp -- --template vue- this will create the folder
vueappwhich is the root folder for the vue app.
- this will create the folder
- Modify
vite.config.jsaccording to Reference 1.- the
build.watchattribute makes the build process go into ‘watch’ state after building build.rollupOptions.outputremoves the hash from the generated filenames during build, which is the default output. This way we can always enqueue the same filename in WordPress.
- the
- Modify
package.jsonaccording to Ref. 1.- this enables the
watch-after-build. - the
--mode=devmakes vite build looks for a.env.devfile. In there we set the environment variableNODE_ENVto “development“. This way the build will be in development mode and you will still have the vue dev-tools working in your browser console.
- this enables the
- Create a file named
.env.devunder the vue project directory (i.e., vueapp) and add below line to the file.NODE_ENV=development
- To build the app for use by the plugin PHP, change directory to
vueappin the VS Code Terminal, then run following commands to create / update files in the /vueapp/dist directory.npm install- n
pm run dev
- under the plugin root directory, run
Install Vue Router
- at the vue root folder (
vueapp), runnpm install vue-router@4 - edit
package.jsonto add"vue-rounter": "^4.3.2"to both “dependencis” and “devDependencies“.
Vue app file structure
The directory /vueapp/src contains all the files and subdirectories for development.
- file
/vueapp/src/main.js– see below section. - file
/vueapp/src/App.vue– the top level Single Page Application (SPA) template that will be loaded to the mount-point.<router-view name="tab">is to load the component namedtabif it is defined in the components for the path.<router-view>is to load the default component defined for the path.
- file
/vueapp/src/style.css– the css file for the whole app as it is unscoped. - directory
/vueapp/src/assets– contains images, fonts and other assets. - directory
/vueapp/src/components– are templates which will be called by the templates in /vueapp/src/views - directory
/vueapp/src/composables– contains js scripts for functions to be reused in multiple views or components. Currently only theuseFetch.jsis used which is toGETan option value using the WP Rest API. - directory
/vueapp/src/views– templates pointed by routes of router.
The master file: main.js
This file manages the operation of the vue app at the top level.
- import the necessary functions from
vue. - import components from directory
views. - define the
routeswhich maps the paths to the corresponding components for display. - create the
router. - the command
createApp(App).use(router).mount("#vue-plugin-1-app")is to create the app using the template inApp.vue, then apply the createdrouterand mount the app to the mount point#vue-plugin-1-app.
Composable useFetch.js
- composables are functions that can be reused by multiple components.
- this javascript file with extension js makes use of the
fetch()to access the API endpoint to GET an option value from the database table. - the
fetch()sends the headerX-WP-Noncewhich contains the nonce for remote authentication. The nonce is generated inCreateMenuPages.phpand passed to JS in array$jsDatausing thewp_localize_script()function.- reference: Permissions Callback
- reference: Using the Fetch API
- note the use of 2
awaitin thetryblock. The first await is to wait for the hearder to arrive from the server and the second is to wait for the body stream completion. If the.then()approach is used, only oneawaitis required.
Fetch & display data in ListAll.vue / ListData.vue
ListAll.vuehas aSuspenseblock within which are thedefaulttemplate andfallbacktemplete.- the
defaulttemplate loads the componentListData.vue. While waiting for theListData, thefallbacktemplate displays theLoading...message. ListData.vuecalls the composableuseFetch()which returns a Proxy object.- use
isProxy()to check the returned result is a proxy. - use
toRaw()to convert the proxy object to a real object.
- use
- note that if the component contains an
awaitfor a Promise to return, vue will not render the component and give a warning that the component should be in aSuspenseboundary of the parent template. This is the reason for setting up the two views to call their own child in the/componentsdirectory.
Display and update data in AddNew.vue / DataForm.vue
AddNew.vuehas a Suspense block within which are thedefaulttemplate andfallbacktemplete.- the
defaulttemplate loads the child componentDataForm.vue. While waiting for the form, the fallback template displays theLoading.... message. - the propose of
isProxy()andtoRaw()are same as the above section.
WordPress debug tips
It is possible to write message to the debug.log under the wp-content directory.
- Enable the wp debug mode. Use the function
error_log(print_r($var, true))to log the variable to thedebug.log. - When the
print_r()second parameter is true, the function returns the result instead of printing it on screen.
Simulate fetch().catch (error)
The catch error is mainly on the network connection issue. This can be simulated by disabling the network adapter. WordPress is running on the local host without the need of network. The API endpoint is using HTTP protocal which requires network connection. The simulation is possible by disconnecting the network.
Downloads
The files downloaded from here are for development propose. For production plugin installation, all files and directories under /vueapp should be removed except the directory /vueapp/dist. The plugin files for various versions are also kept in MJ1900:\doc\projs\60-tf-practice\14-plugins-Dev\53-vue-plugin-1-template.
Click the links below to download different versions.
- Version 1.3.1
- First release.
Reference
- Integrating Vue in a WordPress plugin – Michael Sperber
- The pfd copy can be found in the plugin dev folder (60/14/53)
- YouTube- Load Vue.js in WordPress plugin [EASY GUIDE] by codingoblin
- How to use Vite with WordPress – Accreditly
- Good practices and Design Patterns for Vue Composables – Jakub Andrzejewski
- How JavaScript Promises Work – Nathan Sebhastian
- How to Use Async/Await in JavaScript – Nathan Sebhastian
