My computed values get function is never run when the vm is created, so the value it should have is never assigned.
Strangely enough it's run if I try to access the value, it is just never run when the app starts.
Basically I'm supposed to be computing the value of the skin and on startup or when changed the stylesheet link should be altered to load the correct skin.
It works great, the problem is that it's not being run on startup. Do I have to use some hacky solution, like, getting the value once in the mounted function of the vm? Or am I missing something here...
computed: {
skin: {
get: function () {
var mySkin = "my/skin/string";
if (window.localStorage.getItem("skin") === null) {
window.localStorage.setItem("skin", mySkin);
jquery("link[id='skin']").attr("href", "css/skins/" + mySkin + ".css");
} else {
mySkin = window.localStorage.getItem("skin");
window.localStorage.setItem("skin", mySkin);
jquery("link[id='skin']").attr("href", "css/skins/" + mySkin + ".css");
}
var hey = this.skin;
return mySkin;
},
set: function (val) {
window.localStorage.setItem("skin", val);
jquery("link[id='skin']").attr("href", "css/skins/" + val + ".css");
}
}
}
Computed property is a property which depends on data properties.
In your case it is better IMO to create a data property skin, and a method like setSkin() which initializes the skin data property, and another method saveSkin() to save your property into localStorage.
You then can run setSkin() in mounted() hook.
Template:
new Vue({
el: '#app',
data: {
skin: ''
},
methods: {
setSkin: function() {
this.skin = ...;
},
saveSkin: function() { // call this function when you need to save skin data into localStorage
window.localStorage.setItem("skin", this.skin);
....
}
},
mounted() {
this.setSkin(); // retrieve skin data from localStorage, etc.
}
});
Related
I have a function inside the methods property which takes passedData value from the data() method, does some changes to the object and stores the new value in a constant
this somehow causes a side effect which changes the passedData value also.
What is the cause and how can I prevent that ?
this.passedData: -->
{"propss":"value"}
App.vue?3dfd:61 {"propss":"propss : value"}
App.vue?3dfd:49 {"propss":"propss : value"}
App.vue?3dfd:61 {"propss":"propss : propss : value"}
new Vue({
el: "#app",
data() {
return {
passedData: { propss: "value" },
};
},
methods: {
displayData() {
console.log(JSON.stringify(this.passedData));
const root = d3.hierarchy(this.passedData, function(d) {
if(typeof d == "object")
return Object.keys(d).filter(d=>d!="$name").map(k=>{
if(typeof d[k] == "object") d[k].$name = k;
else d[k] = k + " : " + d[k];
return d[k];
});
})
console.log(JSON.stringify(this.passedData));
},
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button type="button" #click="displayData">display</button>
</div>
The problem you are facing here is, that data in vue.js is reactive. So changing data stored anywhere will change the source of the data too.
If you want to change something without facing issues due to reactivity, you need to assign this data without reference as you need. Small example:
// Passing your reactive data
passedData: { propss: "value" },
newData: {}
// Passing data to new property and reactivity
this.newData = this.passedData
this.newData.propss = 'newValue' // this will change passedData.propss to "newValue" too
// Assign Data without reference
this.newData = JSON.parse(JSON.stringify(this.passedData))
If you now change newData it wont affect passedData, as JSON.parse(JSON.stringify(this.passedData)) created a new state without reference to the original.
Note that this should only be a workaround, it isnĀ“t a proper state management.
When I am watching the distance computed property below, that data returned is constant rather than just when the data is changed.
Is this meant to happen because I am setting then watching or is there an error? I want to watch and then reformat some the of outputted data when the computed property is changed by the user.
computed: {
distance: {
get: function() {
let location = parseFloat(this.radius_search.lat) + ', ' + parseFloat(this.radius_search.lng);
return this.search_store.queryParameters = { aroundLatLng: location, aroundRadius: this.radiusToMetres };
},
set(value) {
return this.search_store.queryParameters = this.distance;
},
},
radiusToMetres(){
return this.radius * 1600;
},
},
watch: {
distance: function(value) {
console.log(value);
},
},
I have a screen shot of the console output below.
You are assigning a value in a getter, which is a bad practice. Getters should have no side effects.
In particular, you are creating a new object and assigning it to the return value, which is causing the loop: distance gets a new value every time it is looked at, and every time it gets a new value, its dependencies (like your watcher) are looking at it.
So I'm trying to do a live search of some client-side info in Meteor.
I have
Template.userTable.events({
"change #nameSearchBar":function(event){
//console.log(event);
searchText = event.target.value;
filteredUsers = Meteor.users.find({"profile.name":{$regex: (".*"+searchText+".*") } });
console.log(filteredUsers.fetch());
}
});
In my js, and
Template.userTable.helpers({
usersOnline: function() {
return filteredUsers;
}
});
As well. I can see that filteredUsers is updated in the console logs, but I don't get the nice live-update of the html that lists usersOnline - instead I just get all of them, which is what the usersOnline was initialised to, by calling filteredUsers = Meteor.users.find().
How can I get the desired live-update?
Your filteredUsers variable is not reactive, so when it changes, nothing is telling the usersOnline helper to re-run. I think you can do this in one of two ways:
Use a ReactiveVar. I'm admittedly not very experienced with them, but I think you could assign the ReactiveVar to be part of the Template, and then have it watch that -- something like:
Template.userTable.created = function() {
this.data.filteredUsers = new ReactiveVar(...) // initialize this to whatever
}
Template.userTable.helpers({
usersOnline: function() {
return this.filteredUsers.get(); // pulling from the reactive var rather than a static var
}
});
Template.userTable.events({
"change #nameSearchBar":function(event){
searchText = event.target.value;
// Setting the reactive var should invalidate the "get" in the helper and trigger re-run
filteredUsers.set(Meteor.users.find({"profile.name":{$regex: (".*"+searchText+".*") } }));
}
});
Use a Session variable -- very similar, but it's accessible globally instead of set on that Template. All Session variables are reactive by default:
Template.userTable.created = function() {
Session.setDefault('filteredUsers', ...) // initialize this to whatever
}
Template.userTable.destroyed = function() {
Session.set('filteredUsers', null); // clean up after yourself when you navigate away
}
Template.userTable.helpers({
usersOnline: function() {
return Session.get('filteredUsers'); // pulling from Session var, which is reactive
}
});
Template.userTable.events({
"change #nameSearchBar":function(event){
searchText = event.target.value;
// Setting the Session var should invalidate the "get" in the helper and trigger re-run
Session.set('filteredUsers', Meteor.users.find({"profile.name":{$regex: (".*"+searchText+".*") } })); }
});
Like I said, I haven't done a lot with ReactiveVars, but I think #1 is technically the better way to go, so I'd play around with that first.
You can also define the searchtext in a Session variable and when that variable changes, display the new result.
Something like this:
Session.setDefault('searchText', null);
Template.userTable.events({
"change #nameSearchBar":function(event){;
searchText = event.target.value;
Session.set('searchText', searchText);
}
});
Template.userTable.helpers({
usersOnline: function() {
if(Session.get('searchText') == null){
return Meteor.users.find();
} else {
var searchText = Session.get('searchText');
return Meteor.users.find({"profile.name":{$regex: (".*"+searchText+".*") } });
}
}
});
I am having some issues trying to work out what is going ok with MVC SPA and Knockout.
When you create a new project some files are created for knockout.js as examples, but I am struggling to understand what is going on.
Primarily the issue is with the app.viewmodel.js and the function AddViewModel.
Here is some code which I will attempt to breakdown:
self.addViewModel = function (options) {
var viewItem = {},
navigator;
// Example options
//{
// name: "Home",
// bindingMemberName: "home",
// factory: HomeViewModel
//}
// Add view to AppViewModel.Views enum (for example, app.Views.Home).
self.Views[options.name] = viewItem; // Don't really get this, seems to add a blank object to app.Views.Home
// Add binding member to AppViewModel (for example, app.home);
self[options.bindingMemberName] = ko.computed(function () {
//if (self.view() !== viewItem) {
// console.log(self.view()); // returns {}
// console.log(viewItem); // returns {}
// return null; // should never hit this?
//}
return new options.factory(self, dataModel); // This adds our ViewModel to app.home, app.login, etc
});
// This checks to see if we have defined a navigatorFactory in our viewmodel (AddViewModel)
if (typeof (options.navigatorFactory) !== "undefined") {
navigator = options.navigatorFactory(self, dataModel);
} else {
navigator = function () {
console.log(viewItem);
self.view(viewItem);
};
}
// Add navigation member to AppViewModel (for example, app.NavigateToHome());
self["navigateTo" + options.name] = navigator;
};
ok, so let's start. First of all we declare 2 variables:
var viewItem = {},
navigator;
viewItem is set as a blank object and navigator is undefined.
The first thing we do, is set self.Views[options.name] to our viewItem, so in my understanding, this would mean:
self.Views.Home = {}
If we look at the declaration in app.viewmodel.js self.Views looks like this:
self.Views = {
Loading: {} // Other views are added dynamically by app.addViewModel(...).
};
So in here there is already a view called Loading. So I am confused as to what is actually happening here.
The next bit of code creates a function:
self[options.bindingMemberName] = ko.computed(function () {
return new options.factory(self, dataModel);
});
This is a lot easier to understand. It basically takes our ViewModel and adds it to a function under the name of self.home (or whatever the bindingMemberName of our ViewModel is.
This next piece is what confuses me:
if (typeof (options.navigatorFactory) !== "undefined") {
navigator = options.navigatorFactory(self, dataModel);
} else {
navigator = function () {
console.log(viewItem);
self.view(viewItem);
};
}
// Add navigation member to AppViewModel (for example, app.NavigateToHome());
self["navigateTo" + options.name] = navigator;
If I strip this down, it basically says if we define a navigatorFactory, then the navigator (which is currently undefined!) is equal to our navigatorFactory. That bit is easy.
It's the next bit I don't get.
It says, else, the navigator is a function that returns our self.view(viewItem) (remember that viewItem is just a blank object.
Then we set self["navigateTo" + options.name] = navigator.
So in english, this looks like it is saying, get our blank viewItem, assign it to self.view for every ViewModel we add. Then assign a function returning our self.view(viewItem) to our navigator variable (which is currently undefined) and assign this to our self.naviateToHome() (or whatever).
So to me, that looks like self.navigateToHome(), self.navigateToLogin(), self.navigateToTimbucktoo() would all return the same function with the same self.view.
So, can anyone explain to me what is actually happening?
Update 1
So, I have figured some things out. First things first, the navigator is setting the current view, so basically self.Views looks like this after all the models are added:
self.Views = {
Loading: { },
Home: { },
Login: { }
}
So even though self.view() returns an empty object, it isn't the same as the viewItem because it is stored with the name into self.Views.
So, the navigator is actually applying the viewItem to self.views.
I tested this out by changing the viewItem to this:
var viewItem = { options.name }
and sure enough, self.Views looked liked this:
self.Views = {
Loading: { },
Home: { name: "Home" },
Login: { name: "Login" }
}
so when we set self.view using our navigator, the function is called (app.home for example) and it runs the code to return our factory or null if it isn't the current view.
Im trying to store the stats of 'this' in my javscript object so that later on in my application I can return 'this' to a previous state. I thought I could accomplish using a closure but so far I haven't successful. My idea was to do something like this
function SavedFeature() {
var self = this;
this.savedItem;
this.storeState = function() {
this.savedItem = storeClosure();
}
function storeClosure() {
var closure = self;
return function() {
return closure;
};
};
//other things the user can change...
}
so later on in my application if I needed to return to the point when I called storeState I could just do
//return the object I put in my closure
var backToNormal = savedFeature.savedItem();
that doesn't work though because any changes to my savedFeature object after I call storeState() are being reflected in the item im retrieving from called savedItem(). I'm guessing this is happening because closure is being set to a reference of self instead of copied to a new instance.
Is there anyway to store the state of my entire object in a closure like this or do I need to store this some other way.
The issue you are running into is that in js objects are passed by reference. This means that all changes performed on your object will apply to your obj.savedItem property.
Fix: Store a deep clone into obj.savedItem
this.storeState = function() {
this.savedItem = _.cloneDeep(this); // or _.clone(this, true);
}
cloneDeep is a lodash method, most js libs supply one of their own, e.g. jQuery's $.extend, etc.
You could easily roll your own deep clone function, look up the options on this thread.
A complete example with jQuery:
function SavedFeature() {
this.savedItem;
this.clone = function() {
return $.extend(true, {}, this);
},
this.storeState = function() {
this.savedItem = this.clone();
}
}
Doing it this way allows you adapt to different environments by changing your clone method as it is facading the used library method.
There are dozens of ways how to implement it. I will do just simple one. saving property.
Take into account if you want to save entire object you need to do deep copy of the object.
this is your feature:
function SavedFeature() {
this.savedItem = {'isNew': true};
this.stateMachine = new StateMachine();
}
this is some kind of state machine:
function StateMachine () {
var state = { 'isNew' : null};
function set(newState) {
state.isNew = newState.isNew;
}
function get() {
return state.isNew;
}
return {
get : get,
set : set
};
}
which, know how to store isNew property
and a working sample:
var savedFeature = new SavedFeature();
console.log(savedFeature.savedItem); // true by default
savedFeature.stateMachine.set(savedFeature.savedItem); // saving state.
savedFeature.savedItem.isNew = false; // modifying state
console.log(savedFeature.savedItem); // return false, because of statement above
var restoredState = savedFeature.stateMachine.get(); // restoring state
console.log(restoredState); // true
savedFeature.savedItem.isNew = restoredState.isNew;
console.log(savedFeature.savedItem); // true
you can adjust that code, and reach functionality whatever you need. hope that helps