I have a rich AJAX-based web application that uses JQuery + Knockout. I have a JQuery plugin that wraps my Knockout view models to expose utility methods like .reset(), .isDirty(), and so on.
I have a method called .setBaseline() that essentially takes a snapshot of the data model once it has been populated (via the mapping plugin). Then I can use this snapshot to quickly determine if the model has changed.
What I'm looking for is some kind of general purpose function that can return an object that represents the differences between two 2 JavaScript objects where one of the objects is considered to be the master.
For example, assume that this is my snapshot:
var snapShot = {
name: "Joe",
address: "123 Main Street",
age: 30,
favoriteColorPriority: {
yellow: 1,
pink: 2,
blue: 3
}
};
Then assume that the live data looks like this:
var liveData = {
name: "Joseph",
address: "123 Main Street",
age: 30,
favoriteColorPriority: {
yellow: 1,
pink: 3,
blue: 2
}
};
I want a .getChanges(snapShot, liveData) utility function that returns the following:
var differences = {
name: "Joseph",
favoriteColorPriority: {
pink: 3,
blue: 2
}
};
I was hoping that the _.underscore library might have something like this, but I couldn't find anything that seemed to work like this.
I don't think there is such a function in underscore, but it's easy to implement yourself:
function getChanges(prev, now) {
var changes = {};
for (var prop in now) {
if (!prev || prev[prop] !== now[prop]) {
if (typeof now[prop] == "object") {
var c = getChanges(prev[prop], now[prop]);
if (! _.isEmpty(c) ) // underscore
changes[prop] = c;
} else {
changes[prop] = now[prop];
}
}
}
return changes;
}
or
function getChanges(prev, now) {
var changes = {}, prop, pc;
for (prop in now) {
if (!prev || prev[prop] !== now[prop]) {
if (typeof now[prop] == "object") {
if(c = getChanges(prev[prop], now[prop]))
changes[prop] = c;
} else {
changes[prop] = now[prop];
}
}
}
for (prop in changes)
return changes;
return false; // false when unchanged
}
This will not work with Arrays (or any other non-plain-Objects) or differently structured objects (removals, primitive to object type changes).
Posting my own answer so folks can see the final implementation that also works with arrays. In the code below, "um" is my namespace, and I'm also using the _.isArray() and _.isObject methods from Underscore.js.
The code that looks for "_KO" is used to skip past Knockout.js members that are present in the object.
// This function compares 'now' to 'prev' and returns a new JavaScript object that contains only
// differences between 'now' and 'prev'. If 'prev' contains members that are missing from 'now',
// those members are *not* returned. 'now' is treated as the master list.
um.utils.getChanges = function (prev, now) {
var changes = {};
var prop = {};
var c = {};
//-----
for (prop in now) { //ignore jslint
if (prop.indexOf("_KO") > -1) {
continue; //ignore jslint
}
if (!prev || prev[prop] !== now[prop]) {
if (_.isArray(now[prop])) {
changes[prop] = now[prop];
}
else if (_.isObject(now[prop])) {
// Recursion alert
c = um.utils.getChanges(prev[prop], now[prop]);
if (!_.isEmpty(c)) {
changes[prop] = c;
}
} else {
changes[prop] = now[prop];
}
}
}
return changes;
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With