How can I change props on the root Vue instance? - vue.js

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.

Related

Vue.js global event handling

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.

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 to pass props to component when used with Quasar dialog plugin

According to quasar docs the custom component used with dialog plugin can have props. But I don't see how to pass those props to the component via
this.$q.dialog({component: CustomComponent,})
The docs state you can add them after the comma like this:
this.$q.dialog({
component: CustomComponent,
// optional if you want to have access to
// Router, Vuex store, and so on, in your
// custom component:
parent: this, // becomes child of this Vue node
// ("this" points to your Vue component)
// (prop was called "root" in < 1.1.0 and
// still works, but recommending to switch
// to the more appropriate "parent" name)
// props forwarded to component
// (everything except "component" and "parent" props above):
text: 'something',
// ...more.props...

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.

Vuejs - require is not defined

I am just playing around with vuejs router and try to load a component.
I used the sample code and changed foo
// Define some components
var Foo = Vue.extend({
template: require('./components/test.vue')
});
var Bar = Vue.extend({
template: '<p>This is bar!</p>'
});
// The router needs a root component to render.
// For demo purposes, we will just use an empty one
// because we are using the HTML as the app template.
var App = Vue.extend({})
// Create a router instance.
// You can pass in additional options here, but let's
// keep it simple for now.
var router = new VueRouter()
// Define some routes.
// Each route should map to a component. The "component" can
// either be an actual component constructor created via
// Vue.extend(), or just a component options object.
// We'll talk about nested routes later.
router.map({
'/foo': {
component: Foo
},
'/bar': {
component: Bar
}
})
// Now we can start the app!
// The router will create an instance of App and mount to
// the element matching the selector #app.
router.start(App, '#app')
I also tested it with
Vue.component('Foo', {
template: require('./components/test.vue')
})
In my test.vue i have
<template>
<h2>Test</h2>
</template>
But not as soon as i use require i get everytime the error Required is not defined in my dev tools.
What do i wrong here?
require is a builtin in the NodeJS environment and used in Grunt build environments.
If you also want to use it in a browser environment you can integrate this version of it: http://requirejs.org
(Author) This is outdated:
Use Browserify or Webpack as there is active support in the Vue community
http://vuejs.org/guide/application.html#Deploying_for_Production (dead link)
I personally used this repo of the Vue GitHub-org to get started quickly.
Edit:
This has moved on a bit in early 2018.
Deployment guide: https://v2.vuejs.org/v2/guide/deployment.html
'getting started' type repo: https://github.com/vuejs/vue-loader