Tracking data changes in Vue.js on a Javascript that already exists? - javascript

I have a project that I've been working on for awhile that I might want to use vuejs for some of the UI elements. I fired up a tutorial and tried to piece together a basic example.
I have a basic javascript object like so:
var hex = {};
hex.turn_phase = 'unit_placement';
On my template, I have:
var app = new Vue({
el: '#vue-test',
data: {
message: 'Hello Vue!',
turn_phase: hex.turn_phase,
},
delimiters: ["<%","%>"],
mounted: function() {
this.fetch_turn_phase();
},
methods: {
fetch_turn_phase: function() {
Vue.set(this, 'turn_phase', hex.turn_phase);
},
}
});
This renders the correct turn_phase on the template initially, but if I change the hex.turn_phase in the browser console, the template doesn't react.
Is there something that I missed in this basic example?

It looks like you may have made things unnecessarily difficult. Just access your Vue instance via app?
Always make sure you go through the setters generated by Vue.js. These are watched and will re-render your component.
Instead, try using
app.turn_phase = 'unit_placement';
You can get a better understanding here, Reactivity

Vue creates all the data variables, computed properties and values returned from methods reactive. in your case since you
are changing hex, which is not a vue variable, so vue will not detect any changes in this variable. However if you change message variable, it will be reflective and will be changed in the template.

Related

Global data with VueJs 2

Im relatively new with VueJS, and I've got no clue about how to make some data globally available. I would like to save data like API endpoints, user data and some other data that is retrieved from the API somewhere where each component can get to this data.
I know I can just save this with just vanilla Javascript but I suppose there is a way to do this with VueJS. I may be able to use the event bus system to get the data but I don't know how I can implement this system to my needs.
I would appreciate it if somebody can help me with this.
Make a global data object
const shared = {
api: "http://localhost/myApi",
mySharedMethod(){
//do shared stuff
}
}
If you need to expose it on your Vue, you can.
new Vue({
data:{
shared
}
})
If you don't, you can still access it inside your Vues or components if you've imported it or they are defined on the same page.
It's really as simple as that. You can pass shared as a property if you need to, or access it globally.
When you're just starting out there is no real need to get complicated. Vuex is often recommended, but is also often overkill for small projects. If, later, you find you need it, it's not that hard to add it in. It's also really for state management and it sounds like you just really want access to some global data.
If you want to get fancy, make it a plugin.
const shared = {
message: "my global message"
}
shared.install = function(){
Object.defineProperty(Vue.prototype, '$myGlobalStuff', {
get () { return shared }
})
}
Vue.use(shared);
Vue.component("my-fancy-component",{
template: "<div>My Fancy Stuff: {{$myGlobalStuff.message}}</div>"
})
new Vue({
el: "#app"
})
Now, every Vue you create and every component has access to it. Here is an example.
You can use Store which will hold your application state.
const store = new Vuex.Store({
state: {
userData: []
},
mutations: {
setUserData (state, data) {
state.userData = data
}
}
})
With this you can access the state object as store.state, and trigger a state change with the store.commit method:
store.commit('setUserData', userData)
console.log(store.state.userData)
Vue Mixin
// This is a global mixin, it is applied to every vue instance.
// Mixins must be instantiated *before* your call to new Vue(...)
Vue.mixin({
data: function() {
return {
get $asset() {
return "Can't change me!";
}
}
}
})
template
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
In Root: {{globalReadOnlyProperty}}
<child></child>
</div>
Or
Vue.prototype.$asset = 'My App'
I just use an environment.js file to store all of my endpoints as object properties.
var urls = {};
urls.getStudent = "api/getStudent/{id}";
etc...
Then I put reference to this environment.js file in the head of document on pages where I have VueJS code that needs access to those endpoints. Im sure there are many ways to do this.

Return vue instance from vue.js filter (Vue.js 2)

I'm using filter function for internationalization like this:
<div>{{ "hello" | message }}<div>
message is a filter function that depends on global Vue.config.lang variable.
It works great, but if I change Vue.config.lang, message doesn't rerender.
I wanted to make message rerender anytime Vue.config.lang changes, so I changed my filter function from
message => locale_messages[Vue.config.lang][message]
to
message => new Vue({
template: '{{ message }}',
computed: {
message() { return locale_messages[Vue.config.lang][message]; }
}
})
But it doesn't work. Getting this error:
Uncaught TypeError: Converting circular structure to JSON
at Object.stringify (<anonymous>)
....
Is there anything I can do to make it work? I'm new to Vue.js and can't find a working solution.
Like Evan says, Filters should be pure, so thay can't use a global variable as key to get values from externals arrays. Because of side effects.
So, there is three solutions at your problem that comes in my mind :
Replace filters by methods.
Use vue-i18-n, a simple and powerful module for translation
Use a store system (vuex) wich provides you getters, and helps you manage a global state.
Personnaly I love to use vuex and vue-i18-n together.
In that way I can centralize my data and the language in use. I can also serve specific data in several languages using the store, and let vue-i18-n cares about all the strings in the project.
New to Vue myself, so not quite sure how global variables work, but you can definitely pass params to a custom filter - even a Vue reference. You can do this:
<!-- this = a reference to the vue instance -->
<span class="display-3">{{value|FormatValue(this)}}</span>
[...]
props: ["aIsPercentNeeded"],
[...]
Vue.filter("FormatValue", function (aValue, aVueInstance)
{
if (!aVueInstance["aIsPercentNeeded"])
{
return aValue;
}
return aValue + "%";
});

Why does v-if not get affected when the data is updated later?

If I were to create an element such as that below:
<div id="helloWorldDiv" v-if="visible">
Hello World
</div>
and then create a Vue instance for it:
var helloWorld = new Vue({
el: "#helloWorldDiv",
data: {
visible: false
}
});
I would expect that the line 'helloWorld.visible = true;' would show the element, but it has not effect. Can anyone explain why this doesn't work?
JSFiddle
I think you are getting started with Vue.js, which is great!
There are several changes you need to do in your sample app. Check this fiddle: https://jsfiddle.net/mani04/f20Ld806/
As you can see in that example, your show() function needs to be defined inside the methods of your Vue app. Also the show button needs be part of the app template, not outside of it.
You can find a lot more in Vue docs and tutorials online.
First off, I would suggest not manipulating the Vue instance from the outside. It'll make it harder to maintain the app as it grows.
However, as to your question, there's a number of issues with your JSFiddle:
You never set visible to true.
In your show function, you misspelled helloWorld as helloWolrd.
The show function and helloWorld variable need to be attached to the window so they can be globally accessed from the onclick event.
So if you update your javascript to:
var helloWorld = new Vue({
el: "#helloWorldDiv",
data: {
visible: false
}
});
function show() {
helloWorld.visible = true;
};
window.helloWorld = helloWorld;
window.show = show;
Your code works as expected.

Adding new keys generated by a variable to a object in Vue

Building a project using Vue.js (and Laravel), the following (greatly simplified) code results in the below error:
Vue component:
<template>
<input type="text" class="form-control" v-model="main_object[item_id][my_answer_key]">
</template>
<script>
export default {
props: [
],
data() {
return {
main_object: {},
item_id: '1234',
my_answer_key: '5678'
}
},
ready: function () {
vm = this;
},
methods: {
}
}
</script>
Error received:
We know you can use the vm.$set() method to add properties to the object. However, we’re building the model path on the fly (item_id and my_answer_key change depending on various user options being selected). It seems like we have to write a method that determines if the object property is already set, and if it's not set, to then set it. Is there a better way to accomplish the above?
You could seemingly get around this by using the created hook and $set:
created: function () {
this.$set(this.item_id + "." + this.my_answer_key, "")
}
However, if item_id and my_answer_key can change, this approach will ultimately not work because Vue does not have dynamic two way binding. In other words, the binding in your v-model will be created once and will not change later if either of the item_id or my_answer_key values change.
So, to accomplish something like this, you might need to resort to a kludge like using a v-if and toggling it to destroy and recreate the input (and it's binding). That might work.
Sometimes, computed's can help with these situations. Bind your input to a simple attribute on your data model and use a computed to generate the actual nested data model you need elsewhere.

Why is my template helper that uses session.get not reactive?

In my client code I have a Session which is set to an object in a global function:
somefunctions.js
updateTimelineItem = function(newSelection){
var selectedItem = $.grep(Session.get('liveProjectData').items,function(e){return e.position ==newSelection.parent().index()});
Session.set('selectedItem',selectedItem[0]);
};
However, in a template file where I need to display portions of this session's object data, my helper does not fire after the session is set.
mytemplate.js
Template.mytemplate.helpers({
selectedItem: function(){
console.log('reactive update. new item selected.');
return Session.get('selectedItem');
}
})
Example of what the session stores
Object { position: 0, type: "image", source: "imgur", source-url: "https://dl.dropboxusercontent.com/u…", provider: "magic", animation: "puff", thumb: "https://dl.dropboxusercontent.com/u…", fullsize: "https://dl.dropboxusercontent.com/u…", duration: 1000 }
I have tried to find documentation regarding when a Session would not be reactive without much luck. I know the session is set because I can write Session.get('selectedItem') in a browser console and I get the expected output.
Thank you for any help.
The reason my reactive code was not running is because I did not access the specific reactive variable in the spacebars of that template. Although Template.helpers is a reactive computation, it executes based on usage of reactive vars in the dom. This is unlike other reactive computations.
Information regarding this is hard to come by but it is hinted at in the Meteor documentation for Template.currentData() and its usage inside of helper.
The work I needed to do with the reactive variable was outside of the view and should therefore not be used in Template.helper but instead template.autorun.

Categories