BLACKFYRE
MG—01
LANG
←
ALL POSTS
05 · Writing / 2018
20 NOV 2018 · 4 MIN READ

Laravel Nova & Vuex, the Second Round

Patch Nova.js so tools can register Vuex modules before lift-off, and get a real this.$store in every tool component.

CONTENTS
04 +

UPDATE: After 1.1.8 this solution has some issues which make it conflict with the implementation of the Index view’s filters. This solution wasn’t even recommended to apply in the beginning…

UPDATE 2: There’s a follow-up article which doesn’t involve editing Nova’s source files: Laravel Nova & Vuex… a future proof solution?!

If you found this article, you’ve probably been asking the same questions as I have, and maybe even found my previous article about this topic. Having Vuex on the global Nova object isn’t necessarily the best or most convenient thing to have, so if you’re interested in a deeper, more flexible integration and you’re not afraid of touching the Nova base, then here’s how I did it.

Patching Nova

  1. Go into your nova folder and run npm install to install all the front-end dependencies. If Vuex isn’t among them, add it with npm install vuex --save.
  2. Edit the Nova.js file in your nova/resources/js folder. Here’s my example for it:
nova/resources/js/Nova.js
JS
1import Loading from '@/components/Loading'
2import router from '@/router'
3import axios from '@/util/axios'
4import PortalVue from 'portal-vue'
5import Vue from 'vue'
6import AsyncComputed from 'vue-async-computed'
7import Toasted from 'vue-toasted'
8import Vuex from 'vuex'
9
10Vue.use(PortalVue)
11Vue.use(AsyncComputed)
12Vue.use(Vuex)
13
14Vue.use(Toasted, {
15 router,
16 theme: 'nova',
17 position: 'bottom-right',
18 duration: 6000,
19})
20
21export default class Nova {
22 constructor(config) {
23 this.bus = new Vue()
24 this.bootingCallbacks = []
25 this.vuexStoreModules = {}
26 this.config = config
27 }
28
29 /**
30 * Register a callback to be called before Nova starts. This is used to bootstrap
31 * addons, tools, custom fields, or anything else Nova needs
32 */
33 booting(callback) {
34 this.bootingCallbacks.push(callback)
35 }
36
37 /**
38 * Execute all of the booting callbacks.
39 */
40 boot() {
41 this.bootingCallbacks.forEach(callback => callback(Vue, router))
42
43 this.bootingCallbacks = []
44 }
45
46 /**
47 * Register a Vuex module to be included in the root Vue instance's store
48 * @param {String} moduleName
49 * @param {Object} module
50 */
51 registerVuexStoreModule(moduleName, module) {
52 this.vuexStoreModules[moduleName] = module
53 }
54
55 /**
56 * Start the Nova app by calling each of the tool's callbacks and then creating
57 * the underlying Vue instance.
58 */
59 liftOff() {
60 let _this = this
61
62 this.boot()
63
64 this.app = new Vue({
65 el: '#nova',
66 router,
67 components: {
68 Loading
69 },
70 store: new Vuex.Store({
71 modules: _this.vuexStoreModules
72 }),
73 mounted: function () {
74 this.$loading = this.$refs.loading
75
76 _this.$on('error', message => {
77 this.$toasted.show(message, {
78 type: 'error'
79 })
80 })
81 },
82 })
83 }
84
85 /**
86 * Return an axios instance configured to make requests to Nova's API
87 * and handle certain response codes.
88 */
89 request(options) {
90 if (options !== undefined) {
91 return axios(options)
92 }
93
94 return axios
95 }
96
97 /**
98 * Register a listener on Nova's built-in event bus
99 */
100 $on(...args) {
101 this.bus.$on(...args)
102 }
103
104 /**
105 * Register a one-time listener on the event bus
106 */
107 $once(...args) {
108 this.bus.$once(...args)
109 }
110
111 /**
112 * Unregister a listener on the event bus
113 */
114 $off(...args) {
115 this.bus.$off(...args)
116 }
117
118 /**
119 * Emit an event on the event bus
120 */
121 $emit(...args) {
122 this.bus.$emit(...args)
123 }
124}

The changes compared to the original are small: Vuex is installed on Nova’s own Vue, there’s a vuexStoreModules collection with a registerVuexStoreModule() method to fill it, and liftOff() hands a store built from those modules to the root instance.

The order is what makes it work. liftOff() calls boot() first, so every tool’s booting callback has run, and registered its module, before the store is created.

  1. Build and publish the updated Nova assets, so the application serves your version instead of the original:
SHELL
cd nova
npm run production
cd ..
php artisan vendor:publish --tag=nova-assets --force
  1. Register your Vuex stores from your tools / resource tools and have some fun with it :) Below is an example to do just that.

Registering a store from your tool

The store parts are the same files as in the previous article. The difference is that they’re registered as a module instead of becoming a store of their own:

resources/js/tool.js
JS
1import state from './store/state'
2import actions from './store/actions'
3import getters from './store/getters'
4import mutations from './store/mutations'
5
6Nova.booting((Vue, router) => {
7 Vue.component('your-tool', require('./components/Tool'))
8
9 Nova.registerVuexStoreModule('yourTool', {
10 namespaced: true,
11 state,
12 actions,
13 getters,
14 mutations
15 })
16})

Make the module namespaced. Every tool now shares one store, and without a namespace two tools with a setItems mutation or a loading getter would step on each other’s toes.

Using it in components

Since the store is on the root instance now, this.$store works in every component, and so do Vuex’s map helpers:

resources/js/components/Tool.vue
VUE
1<template>
2 <div>
3 <heading class="mb-6">Your Tool</heading>
4
5 <card class="p-6">
6 <p v-if="loading">Loading…</p>
7 <p v-else>{{ itemCount }} items</p>
8 </card>
9 </div>
10</template>
11
12<script>
13import { mapActions, mapGetters, mapState } from 'vuex'
14
15export default {
16 computed: {
17 ...mapState('yourTool', ['loading']),
18 ...mapGetters('yourTool', ['itemCount'])
19 },
20
21 methods: {
22 ...mapActions('yourTool', ['fetchItems'])
23 },
24
25 mounted () {
26 this.fetchItems()
27 }
28}
29</script>

No more reaching for a store on the global Nova object, and the Vue devtools show every tool’s module in one place.

Wrapping up

Use this knowledge wisely, because Vuex support for Nova is coming (I hope), and if you start overwriting source files, you’ll have a tough time updating the Nova base.

TAGS
Laravel
Nova
Vue
Vuex
06 · CONTACT

Have a system that needs building?

GET IN TOUCH → PROJECTS →
PRODUCT DATA SHEET
MG—01
BLACKFYRE
S/N MG-1985-1027
Miklós Galicz — Golang Advocate · Solution Architect
MODEL
MG—01 "Miklós Galicz"
SERIES
1985
ORIGIN
Nagykovácsi, Hungary
FUNCTION
Senior Full Stack Engineer · Solution Architect
CORE LANGUAGES
Go · PHP · JavaScript
SPOKEN
Hungarian · English · German
SERVICE LIFE
~20 years in software, ongoing
POWER SUPPLY
Coffee, 2–4 cups / day
DIMENSIONS
1 × human, standard size
OPERATING TEMP.
Calm under production incidents
CONNECTIVITY
[email protected] · github.com/blackfyre · linkedin.com/in/galiczmiklos
Less, but better. Specifications subject to continuous improvement.
● ● ●
MG—01 · SERIES 1985
№ MG-1985-1027
CERTIFICATE OF OPERATION
Certified Operator
Has located every documented feature of the MG—01 without reading the manual. Probably.
TIME
—
FEATURES
—
DATE
—
SIGNED
Miklós Galicz