I have a mobile webview that is injected with some global config object:
Vue.prototype.$configServer = {
MODE: "DEBUG",
isMobile: false,
injected: false,
version: -1,
title:"App",
user: null,
host: "http://127.0.0.1:8080"
}
Later the WebView inject this:
Vue.prototype.$configServer = {
MODE: "DEBUG",
title: "App",
version: "2.0",
isMobile: true,
injected: true,
host: "http://127.0.0.1:8081"
}
And try to use it for the component:
const HomePage = {
key: 'HomePage',
template: '#HomePage',
components: { toolbar },
data() {
return {
items: [
{name:"Login", link:"login"},
]
}
},
computed:{
config() {
return Vue.prototype.$configServer
}
},
};
However the page is not updated. How react to the change?
P.D: I confirm the object is updated with the safari debug tools. Also test in a local html.
Instead of putting the config into the prototype of Vue, you can actually add it as a data option inside the main vue instance which will guarantee you that all your config properties will be reactive. As mentioned in the docs
When you pass a plain JavaScript object to a Vue instance as its data option, Vue will walk through all of its properties and convert them to getter/setters using Object.defineProperty.
Having said that whenever you update your config properties, vue will react to it.
Now let's see how to do it in code:
new Vue(){
data:{ //only place where data is not a function
configServer = {
MODE: "DEBUG",
isMobile: false,
injected: false,
version: -1,
title:"App",
user: null,
host: "http://127.0.0.1:8080"
}
}
}
Now whenever you need to access your config you can directly access it from any component using $root. Like this.$root.configServer.
Well that's it.
I hope it helps.
There are 3 ways to acheive what you want
1- Make sure you import vue in your component
import 'Vue' from vue
...
...
...
computed:{
config() {
return Vue.prototype.$configServer
}
2- If you don't want to import vue the you can directly access prototype using proto from any instance.
computed:{
config() {
return this.__proto__.$configServer
}
3- As you have added the config in the prototype you can actually access is directly from the vue instance using this
computed:{
config() {
return this.$configServer
}
},
Well whatever style matches yours you can choose that.
But I would personally recommend using the 3rd one, because accessing the prototype of instance is sort of an anti-pattern.
I hope it helps.
Related
I am trying to move some functionality to a vue mixin from the component, to be able to use it in multiple components.
This (simplified version of the code) works:
export default {
data() {
return {
file: {},
audioPlayer: {
sourceFile: null,
},
};
},
watch: {
'audioPlayer.SourceFile': function (nextFile) {
console.log('new sourceFile');
this.$data.file = nextFile;
},
}
}
But if I move the audioPlayer data object to a mixin, the watch does no longer fire.
Is this expected behavior?
N.b. I resolved this by directly making the 'file' data property into a computed value, which works in this particular case, but the behavior is still strange.
You need a lowercase s. sourceFile not SourceFile
watch: {
'audioPlayer.sourceFile': function (nextFile) {
console.log('new sourceFile');
this.$data.file = nextFile;
},
}
I'm using Tokbox, a WebRTC SDK and one of their methods returns a video object into a variable.
With Vanilla JS I'd simply use append to add it to my DOM, but with VueJS, I'm not sure how can I accomplish this. I tried using v-html but it outputs the object as a JSON. Here is a screenshot of the object representation on chrome's console:
I don't want to use vanilla JS to append it, I'd rather expect VueJS to convert it to its own Virtual DOM object so I can freely manipulate it and don't worry about wrong states for this object.
I don't know if I'm making sense over here, but I hope you get the idea.
Thanks.
I believe you could use $refs, something like
<video ref="tokboxVideo" />
then
mounted() {
this.$refs.tokboxVideo.srcObject = this.tokboxVideoElementCreated;
}
You might have better success with the streamCreated event on the session. Create a Subscriber component like below:
<template>
<div>
</div>
</template>
<script>
import OT from '#opentok/client';
export default {
name: 'subscriber',
props: {
stream: {
type: OT.Stream,
required: true
},
session: {
type: OT.Session,
required: true
},
opts: {
type: Object,
required: false
}
},
mounted: function() {
const subscriber = this.session.subscribe(
this.stream,
this.$el,
this.opts,
err => {
if (err) {
this.$emit('error', err);
} else {
this.$emit('subscriberConnected', subscriber);
}
}
);
this.$emit('subscriberCreated', subscriber);
}
};
</script>
This will insert the video element inside the component. Then you can control how the component is displayed in your app.
Check out the basic vue sample in our repos.
For some cases, I don't want to use mixins in my Plugin.
I am trying to add a custom Methods like created(), mounted(), methods{}, so I can access its property when the component is loaded & run my custom method.
example: "customMethod"
// #root/home.vue
<script>
export default {
name: 'Home',
props: {
msg: String
},
mounted(){
//
},
methods{
//
},
customMethod{
}
}
</script>
.vue file
<script>
export default {
customMethod () { // Custom option.
console.log('custom method executed!')
},
methods: {},
created () {},
mounted () {}
}
</script>
plugins/customMethods.js
const customMethods = {
install (Vue) {
// INSTALL
if (this.installed) return
this.installed = true
Vue.options = Vue.util.mergeOptions(Vue.options, {
created: function () {
if (this.$options.customMethod) { // If found in the component.
this.$options.customMethod() // Execute the customMethod.
}
}
})
}
}
export default customMethods
main.js
import customMethods from 'plugins/customMethods.js'
Vue.use(customMethods)
What this does is extend the default options for all Vue instances so
the behavior is applied to every single Vue instance created. This is
however undocumented at the moment.
Alternatively, this can also be achieved by the use of global mixin in the plugin. (which you don't want for some reason as per your use case.)
Moreover, one advanced use case is we may need special handling when merging custom option values during Vue.extend. For example, the created hook has a special merge strategy that merges multiple hook functions into an Array so that all of them will be called. The default strategy is a simple overwrite. If you need a custom merge strategy you will need to register it under Vue.config.optionMergeStrategies:
Vue.config.optionMergeStrategies.customMethod = function (parentVal, childVal) {
// return merged value.
}
Every component can access your customMethod if you inject it into Vue.prototype like this:
Vue.prototype.customMethod = function() {}
I'm working with a modular vue application that registers the modules at compile time. Please see the code below -
app.js
import store from './vue-components/store';
var components = {
erp_inventory: true,
erp_purchase: true,
};
// Inventory Module Components
if (components.erp_inventory) {
// erp_inventory.
store.registerModule('erp_inventory', require('./erp-inventory/vue-components/store'));
// erp_inventory/product_search_bar
store.registerModule([ 'erp_inventory', 'product_search_bar' ], require('./erp-inventory/vue-components/store/products/search-bar'));
}
./erp-inventory/vue-components/store/index.js
export default {
namespaced: true,
state() {
return {};
},
getters: {},
actions: {}
}
./erp-inventory/vue-components/store/products/search-bar/index.js
export default {
namespaced: true,
state() {
return {
supplier_id
};
},
getters: {
supplier_id: (state) => {
return state.supplier_id;
}
},
actions: {
set_supplier_id({ commit }, supplier_id) {
commit('set_supplier_id', supplier_id);
}
},
mutations: {
set_supplier_id(state, supplier_id) {
state.supplier_id = supplier_id;
}
}
}
When I use context.$store.dispatch('erp_inventory/product_search_bar/set_supplier_id', e.target.value, {root:true}); to dispatch the action in search-bar/index.js, vue is unable to find the namespace stating [vuex] unknown action type: erp_inventory/product_search_bar/set_supplier_id
I've read the documentation of vuex and dynamic modules and even though I've set namespaced: true, in each store, this problem persists. After dumping the store of my app I found that namespaced was never being set for registered modules (see image below).
Unless I'm doing something wrong, could it be a bug?
You have to use require(....).default, otherwise you won't get the default export pc fro your ES6 module file, but object by webpack that's wrapping it.
I'm attempting to watch for localstorage:
Template:
<p>token - {{token}}</p>
Script:
computed: {
token() {
return localStorage.getItem('token');
}
}
But it doesn't change, when token changes. Only after refreshing the page.
Is there a way to solve this without using Vuex or state management?
localStorage is not reactive but I needed to "watch" it because my app uses localstorage and didn't want to re-write everything so here's what I did using CustomEvent.
I would dispatch a CustomEvent whenever you add something to storage
localStorage.setItem('foo-key', 'data to store')
window.dispatchEvent(new CustomEvent('foo-key-localstorage-changed', {
detail: {
storage: localStorage.getItem('foo-key')
}
}));
Then where ever you need to watch it do:
mounted() {
window.addEventListener('foo-key-localstorage-changed', (event) => {
this.data = event.detail.storage;
});
},
data() {
return {
data: null,
}
}
Sure thing! The best practice in my opinion is to use the getter / setter syntax to wrap the localstorage in.
Here is a working example:
HTML:
<div id="app">
{{token}}
<button #click="token++"> + </button>
</div>
JS:
new Vue({
el: '#app',
data: function() {
return {
get token() {
return localStorage.getItem('token') || 0;
},
set token(value) {
localStorage.setItem('token', value);
}
};
}
});
And a JSFiddle.
The VueJs site has a page about this.
https://v2.vuejs.org/v2/cookbook/client-side-storage.html
They provide an example.
Given this html template
<template>
<div id="app">
My name is <input v-model="name">
</div>
<template>
They provide this use of the lifecycle mounted method and a watcher.
const app = new Vue({
el: '#app',
data: {
name: ''
},
mounted() {
if (localStorage.name) {
this.name = localStorage.name;
}
},
watch: {
name(newName) {
localStorage.name = newName;
}
}
});
The mounted method assures you the name is set from local storage if it already exists, and the watcher allows your component to react whenever the name in local storage is modified. This works fine for when data in local storage is added or changed, but Vue will not react if someone wipes their local storage manually.
Update: vue-persistent-state is no longer maintained. Fork or look else where if it doesn't fit your bill as is.
If you want to avoid boilerplate (getter/setter-syntax), use vue-persistent-state to get reactive persistent state.
For example:
import persistentState from 'vue-persistent-state';
const initialState = {
token: '' // will get value from localStorage if found there
};
Vue.use(persistentState, initialState);
new Vue({
template: '<p>token - {{token}}</p>'
})
Now token is available as data in all components and Vue instances. Any changes to this.token will be stored in localStorage, and you can use this.token as you would in a vanilla Vue app.
The plugin is basically watcher and localStorage.set. You can read the code here. It
adds a mixin to make initialState available in all Vue instances, and
watches for changes and stores them.
Disclaimer: I'm the author of vue-persistent-state.
you can do it in two ways,
by using vue-ls and then adding the listener on storage keys, with
Vue.ls.on('token', callback)
or
this.$ls.on('token', callback)
by using storage event listener of DOM:
document.addEventListener('storage', storageListenerMethod);
LocalStorage or sessionStorage are not reactive. Thus you can't put a watcher on them. A solution would be to store value from a store state if you are using Vuex for example.
Ex:
SET_VALUE:(state,payload)=> {
state.value = payload
localStorage.setItem('name',state.value)
or
sessionStorage.setItem('name',state.value)
}