I am working with ko.mapping plugin in order to map data coming from an ajax request.
Setting the key i expect that subscription is not triggered in this case but it's always raised; i can't understand why. Thx in advance.
var arraySource = [{ Prop: 1, Prop2: 1 }, { Prop: 2, Prop2: 2 }];
var mappedArray = ko.observableArray([]);
mappedArray.subscribe(function (data) {
console.log(data);
});
window.setInterval(function () {
ko.mapping.fromJS(arraySource, {
key: function (data) {
return data.Prop;
}
}, mappedArray);
}, 3000);
Demo: http://jsfiddle.net/xvzAj/
Based on the comment in the docs it sounds like passing the third parameter to .fromJS will overwrite the properties of the array which would trigger the notification.
ko.mapping.fromJS(data, {}, someObject); // overwrites properties on
someObject
Source: http://knockoutjs.com/documentation/plugins-mapping.html
In the knockout.mapping.js ln 627, the array contents are replaced which is triggering the subscription notification.
mappedRootObject(newContents);
https://github.com/SteveSanderson/knockout.mapping/blob/master/build/output/knockout.mapping-latest.debug.js
As #Andrew Walters suggested the subscription will always be triggered, because the entire array is overwritten with the new content.
I found a way to recognoze what really changed by reading the knockout release 3 : http://blog.stevensanderson.com/2013/10/08/knockout-3-0-release-candidate-available/
var myArray = ko.observableArray(["Alpha", "Beta", "Gamma"]);
myArray.subscribe(function(changes) {
// For this example, we'll just print out the change info
console.log(changes);
}, null, "arrayChange");
inside the subscription it's possible to get the added, deleted and retained elements in a very simple way !
Related
I'm trying to save and read multiple json objects from my vue-application with the vue-cookie package (Version 1.1.4). The snippet below runs really well and the json object is saved and retrivied as expected.
However, I noticed as soon as the data is retrieved via cookies and i want to change data inside the object on the web page, the "updated" lifecycle method will not trigger. This behaviour is really awkward as I am only adding the cookieToJson() method to the beforMount() method. The Vue debugger also shows that the value is changed. I think the data is not reactive anymore, but how can I fix this.
data () {
return {
json: {
a: 0,
b: 1,
},
}
},
methods: {
jsonToCookie(name) {
const data = "{\"" + name + "\": " + JSON.stringify(this[name])+"}";
this.$cookie.set(name, data, { expires: '1M' }, '/app');
},
cookieToJson(name) {
const data = JSON.parse(this.$cookie.get(name));
if(data==null) return
for(var i = 0; i < Object.keys(data).length; i++) {
var name = Object.keys(data)[i]
this[name] = data[name];
}
},
beforeMount() {
console.log("beforeMount")
this.cookieToJson("json")
},
updated() {
console.log("updated")
this.jsonToCookie("json")
},
}
In case anyone run into a similar problem, I am using the localStorage now.
I have a form with many fields attached to a data - this.myData:
data: function() {
return {
isDataChanged: false,
myData: {},
myChangedData: {
default: '',
default1: {},
default2: []
},
}
},
myData is populated from a response from the server and it populates the form values.
myChangedData is for the new values, which are changed v-on:input="onChangeMyData($event, 'default')":
onChangeMyData(e, name, required = false){
const val = e.target.value.trim();
this.myChangedData[name] = val;
console.log(this.myChangedData)
this.checkIsmyDataChanged();
},
I can use the same method, providing a key as a second param. With the method checkIsmyDataChanged I am checking is it changed some field in the form. This method loops through myChangedData and compares its properties with changedData and if there is a difference this.isDataChanged = true.
The problem is that, I have a complicated structure of mydata/mydatachanged. default1 has objects in it and default1 is an array of objects. This means that, I can't use onChangeMyData, but other methods with different checks (validations) and now I need to call in all of them this.checkIsmyDataChanged();.
I created a watch for myChangedData:
watch:{
myChangedData: {
handler: function (newVal) {
console.log('change')
},
deep: true
},
},
, but it doesn't execute on change data
Did you try with Vue.set ? Source
Change this.myChangedData[name] = val; to
this.$set(this.myChangedData, 'name', val)
Thanks to that, the modification on the object should be detected and execute the watcher.
So I have the following object structure:
const SamplePalette = {
id: 1,
name: "Sample Palette",
description: "this is a short description",
swatches: [
{
val: "#FF6245",
tints: ["#FFE0DB", "#FFA797"],
shades: ["#751408", "#C33F27"]
},
{
val: "#FFFDA4",
tints: ["#FFFFE1"],
shades: ["#CCCB83"]
},
{
val: "#BFE8A3",
tints: ["#E7FFD7"],
shades: ["#95B77E"]
}
]
}
Let's imagine that this object is managed by the state of my app like this:
this.state = {
currentPalette: SamplePalette,
}
My question is how would I go about updating the val property of a given swatch object in the swatches array? Or more generally - how do I only update pieces of this object?
I tried using the update helper as well as to figure out how Object.assign() works, however I've been unsuccessful and frankly can't really grasp the syntax by just looking at examples.
Also, since I'm going to be modifying this object quite a lot, should I look into maybe using Redux?
[EDIT]
I tried #maxim.sh suggestion but with no success:
this.setState(
{ currentPalette: {...this.state.currentPalette,
swatches[0].val: newValue}
})
Consider you have new new_swatches
I think the clearer way is to get array, update it and put back as:
let new_swatches = this.state.currentPalette.swatches;
new_swatches[0].val = newValue;
this.setState(
{ currentPalette:
{ ...this.state.currentPalette, swatches: new_swatches }
});
Also you have : Immutability Helpers or https://github.com/kolodny/immutability-helper
Available Commands
{$push: array} push() all the items in array on the target.
{$unshift: array} unshift() all the items in array on the target.
{$splice: array of arrays} for each item in arrays call splice() on the target with the parameters provided by the item.
{$set: any} replace the target entirely.
{$merge: object} merge the keys of object with the target.
{$apply: function} passes in the current value to the function and updates it with the new returned value.
I have an Ember Route class defined as below;
export default Ember.Route.extend({
model: function() {
var compObj = {};
compObj.gridPara = this.get('gridPara');
return compObj;
},
gridPara: function() {
var self = this;
var returnObj = {};
returnObj.url = '/myService';
// setting some other returnObj attributes
var summaryObj = {
total: {
label: "Total 1",
value: "100"
},
additional: [{
label: 'Label 2',
value: 'val2'
}, {
label: 'Label 3',
value: 'val3'
}]
};
returnObj.summary = summaryObj;
return returnObj;
},
actions: {
dataLoaded: function(resp) {
// Here I get the service response and want to set (or overwrite) the summaryObj values
this.get('gridParams').summary.total.value = resp.numRows;
}
}
});
My template looks like
{{my-grid params=this.gridPara dataLoaded="dataLoaded"}}
Now I want to set the "summary" on returnObj
I have verified that I get "resp" inside dataLoaded callback.
But I get the following error when trying to do
this.get('gridParams').summary.total.value = resp.numRows;
Uncaught Error: Assertion Failed: You must use Ember.set() to set the value property (of [object Object]) to 100.
Also how do I set/push for "additional" array inside summaryObj
As the error states, you must use set to the the value (Im assuming you have gridParams defined somewhere?):
this.set('gridParams.summary.total.value', resp.numRows);
In order to push a new object, try this:
var additional = this.get('gridParams.additional');
additional.push({label: ..., value: ....});
this.set('gridParams.additional', additional);
not 100% sure, but give it a try:
Watch out the property names. I suppose it's a wording error to declare 'gridPara' and trying to get 'gridParams'
You should retrieve the value like this
this.get('gridParams.summary.total.value')
What you are trying with the last sentence is a setting, but like it was plain JS. In Ember you should do it this.set('gridParams.summary.total.value',resp.numRows)
Just adding to #Remi answers ,the best practice would be to use
Ember.set('gridParams.summary.total.value', resp.numRows);
To answer the question in your comment
Say you want to update additional array at index i.Just do
var updateItem = additional[i];
Ember.set(updateItem.propertyname,newValue)
//Here propertyname would be the property you want to update and new Value is the new value which you want to set to that property
This post is not the same as: Is there a tidy way to define a large watch collection for AngularJS?
My code is (service.js):
var MyJSON = {
array: [
{k:'v'},
{k:'v'},
{k:'v'}
],
info: {
k: 'v',
k2: 'v2'
},
last: 1398680914943 // Date.now()
}
$rootScope.$watchCollection(function () { return MyJSON; }, function (n, o) {
console.log('Change');
});
Detects changes when I work on the root object "MyJSON". Like this:
MyJSON.last = 123456789; // console: Change
But if I am doing something like this:
MyJSON.info.k = 'vDummie';
or:
MyJSON.array.push({k:'something'});
"$watchCollection" does not work.
$watchCollection watches only the 1st level properties; use $watch(..., ..., true) to do "deep" watching. Note: there are 3 arguments, the first two are the same as your code, the third is true for deep watch.