Vue.js global event handling - vue.js

I'm creating a vue.js 2 web app. When some specific global event fires, I want to notify user with some nice dialog. Specifically, I want to notify user, when a new version of service worker is found.
So, in my main.js I have:
reg.addEventListener('updatefound', () => {
//this is where I want to show an info dialog
})
...
//and this is where I start up my Vue app instance
new Vue({el: '#app', render: h => h(App), vuetify, router })
The problem is, how do I tell a Vue component to do something from global scope?
My first suggestion was to define a global vue variable Vue.prototype.$showSwCautionDialog = false and then try to change it somehow from the global scope, but it didnt' work.
I read something about global event bus, but not really sure that that's my case.

Related

VueJS get AJAX data before routing / mounting componenents

I want to initialize some base data once before having VueJS does any routing / mounting.
Now I found out about the Global Navigation Guard router.beforeEach. But It triggers not only on the initial load (page load), but every route that is triggered. Now I could put in some sort of if-statement to have the code run only once, but that's not my preferred way of solving this:
// pseudo:
router.beforeEach((to, from, next) => {
if (state.initialized === false) {
await store.dispatch(init)
}
// the rest of my routing guard logic....
})
I'd prefer not having the if-statement run everytime (knowing it's only going to be true once, and false forever after).
Is there an official way to have (ajax) code run only once, AFTER vue is initialized (so I have access to vuex state, etc.), BEFORE any routing has started.
You can easily perform an asynchronous task before mounting your root Vue instance.
For example
// main.js
import Vue from 'vue'
import store from 'path/to/your/store'
import router from 'path/to/your/router'
import App from './App.vue'
store.dispatch('init').then(() => {
new Vue({
store,
router,
render: h => h(App)
}).$mount('#app')
})
It would be a good idea to show something in index.html while that's loading, eg
<div id="app">
<!-- just an example, this will be replaced when Vue mounts -->
<p>Loading...</p>
</div>

How can I change props on the root Vue instance?

I have a Vue app which implements an embeddable widget (a JupyterLab widget specifically). The widget takes two major properties, which are URLs to images to load.
I know how to set initial props on the root instance:
export default function createWidget(props={}) {
return new Vue({
store,
render: h => h(App, { props })
})
}
How can I change props to trigger a VDOM diffing "re-render"?
I know I can just create a new Vue() app and destroy the old one, but this is really expensive in my case. If I was embedding this as a sub-component in a Vue app, changing a prop and having the regular VDOM diff re-render would be perfect.... now just trying to do this from outside the Vue box.

Exposing a method or property to be accessed from outside of Vue

In Vue's guide they show that you can change the data of your Vue instance created by new Vue directly from the console. But after going deeper into Vue and growing my application, this approach no longer works. As my new Vue instance no longer has that simple el declaration and a direct data property, but instead is now created with the following code:
new Vue({
router,
render: h => h(App)
}).$mount('#app');
Values that get declared in its data property no longer behave like from their guide. I've tried to change this part of the code into:
var app = new Vue({
data: {
test_var: 'Hello world'
},
router,
render: h => h(App)
});
app.$mount('#app');
But calling app.test_var from console reports undefined. I've also tried calling methods from my Vue.mixin() declarations, but they are undefined as well.
What i want is to expose a method through which i could toggle a property on my main config. My use-case would be when users report that something doesn't work properly, i want to be able to say "Go to the console, and just type in this thing to turn debug on, and then tell me what the console says". So if i could do something like app.debug = true or something like that, you know?
EDIT
It seems the problem is not just exposing the data on the Vue instance, but something deeper than that. I don't understand at which point this distinction started, but for example if i were to change the variable name from var app = new Vue()... to var myApp = new Vue()..., then invoking myApp in the console says undefined. The only reason app is not undefined is because it finds the HTML element with the id="app".
So could it have something to do with the "non-simple" initialisation of Vue where i went from just including the Vue script and writing string templates as in that guide, to now having single-file components that are being compiled into some more advanced stuff that are no longer accessible simply through a javascript variable? The project was created with vue-cli, and i am serving it with npm via npm run serve.
Maybe storing your app in window can solve your problem.
var app = new Vue({
data: {
test_var: 'Hello world'
},
methods: {
method() {
console.log(this.test_var)
}
},
router,
render: h => h(App),
});
app.$mount('#app');
window.app = app
Then in console:
app.method()
you can access the data object with the following syntax :
app.$data.test_var
Or :
switch your data to a function that returns an object :
var app = new Vue({
data() {
return {
test_var: 'Hello world'
}
},
router,
render: h => h(App)
});
app.$mount('#app');
app.test_var // 'Hello world'

about beforeCreate in Vuejs

I don't understand purpose of beforeCreate in Vuejs. If it only perform actions before your component has even been added to the DOM then i only add some line code before use vue instance as follow:
<script>
var data ={
c:'',
};
var app= new Vue({
el:"#app",
data:data,
beforeCreate:function(){
data.b="123";
}
});
</script>
I don't understand purpose of beforeCreate hook. Can someone help me?
I simple want understand purpose of beforeCreate
From the docs - "Called synchronously immediately after the instance has been initialized, before data observation and event/watcher setup."
Where to use it? I like to use this lifecycle hook to verify if the user is logged or not (JWT in the localestorage/cookie).

Vuex and Electron carrying state over into new window

I'm currently building an application using Electron which is fantastic so far.
I'm using Vue.js and Vuex to manage the main state of my app, mainly user state (profile and is authenticated etc...)
I'm wondering if it's possible to open a new window, and have the same Vuex state as the main window e.g.
I currently show a login window on app launch if the user is not authenticated which works fine.
function createLoginWindow() {
loginWindow = new BrowserWindow({ width: 600, height: 300, frame: false, show: false });
loginWindow.loadURL(`file://${__dirname}/app/index.html`);
loginWindow.on('closed', () => { loginWindow = null; });
loginWindow.once('ready-to-show', () => {
loginWindow.show();
})
}
User does the login form, if successful then fires this function:
function showMainWindow() {
loginWindow.close(); // Also sets to null in `close` event
mainWindow = new BrowserWindow({width: 1280, height: 1024, show: false});
mainWindow.loadURL(`file://${__dirname}/app/index.html?loadMainView=true`);
mainWindow.once('resize', () => {
mainWindow.show();
})
}
This all works and all, the only problem is, the mainWindow doesn't share the same this.$store as its loginWindow that was .close()'d
Is there any way to pass the Vuex this.$store to my new window so I don't have to cram everything into mainWindow with constantly having to hide it, change its view, plus I want to be able to have other windows (friends list etc) that would rely on the Vuex state.
Hope this isn't too confusing if you need clarification just ask. Thanks.
Although I can potentially see how you may do this I would add the disclaimer that as you are using Vue you shouldn't. Instead I would use vue components to build these seperate views and then you can achieve your goals in an SPA. Components can also be dynamic which would likely help with the issue you have of hiding them in your mainWindow, i.e.
<component v-bind:is="currentView"></component>
Then you would simply set currentView to the component name and it would have full access to your Vuex store, whilst only mounting / showing the view you want.
However as you are looking into it I believe it should be possible to pass the values of the store within loginWindow to mainWindow but it wouldn't be a pure Vue solution.
Rather you create a method within loginWindows Vue instance that outputs a plain Object containing all the key: value states you want to pass. Then you set the loginWindows variable to a global variable within mainWindow, this would allow it to update these values within its store. i.e.
# loginWindow Vue model
window.vuexValuesToPass = this.outputVuexStore()
# mainWindow
var valuesToUpdate = window.opener.vuexValuesToPass
then within mainWindows Vue instance you can set up an action to update the store with all the values you passed it
Giving the fact that you are using electron's BrowserWindow for each interaction, i'd go with ipc channel communication.
This is for the main process
import { ipcMain } from 'electron'
let mainState = null
ipcMain.on('vuex-connect', (event) => {
event.sender.send('vuex-connected', mainState)
})
ipcMain.on('window-closed', (event, state) => {
mainState = state
})
Then, we need to create a plugin for Vuex store. Let's call it ipc. There's some helpful info here
import { ipcRenderer } from 'electron'
import * as types from '../../../store/mutation-types'
export default store => {
ipcRenderer.send('vuex-connect')
ipcRenderer.on('vuex-connected', (event, state) => {
store.commit(types.UPDATE_STATE, state)
})
}
After this, use the store.commit to update the entire store state.
import ipc from './plugins/ipc'
var cloneDeep = require('lodash.clonedeep')
export default new Vuex.Store({
modules,
actions,
plugins: [ipc],
strict: process.env.NODE_ENV !== 'production',
mutations: {
[types.UPDATE_STATE] (state, payload) {
// here we update current store state with the one
// set at window open from main renderer process
this.replaceState(cloneDeep(payload))
}
}
})
Now it remains to send the vuex state when window closing is fired, or any other event you'd like. Put this in renderer process where you have access to store state.
ipcRenderer.send('window-closed', store.state)
Keep in mind that i've not specifically tested the above scenario. It's something i'm using in an application that spawns new BrowserWindow instances and syncs the Vuex store between them.
Regards
GuyC's suggestion on making the app totally single-page makes sense. Try vue-router to manage navigation between routes in your SPA.
And I have a rough solution to do what you want, it saves the effort to import something like vue-router but replacing components in the page by configured routes is always smoother than loading a new page: when open a new window, we have its window object, we can set the shared states to the window's session storage (or some global object), then let vuex in the new window to retrieve it, like created() {if(UIDNotInVuex) tryGetItFromSessionStorage();}. The created is some component's created hook.