Skip to content

Instantly share code, notes, and snippets.

@beyond-code-github
Created January 31, 2014 22:42
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save beyond-code-github/8744757 to your computer and use it in GitHub Desktop.
Save beyond-code-github/8744757 to your computer and use it in GitHub Desktop.
Change tracking implementation for knockout.js supporting complex objects
var getObjProperties = function (obj) {
var objProperties = [];
var val = ko.utils.unwrapObservable(obj);
if (val !== null && typeof val === 'object') {
for (var i in val) {
if (val.hasOwnProperty(i)) objProperties.push({ "name": i, "value": val[i] });
}
}
return objProperties;
};
var traverseObservables = function (obj, action) {
ko.utils.arrayForEach(getObjProperties(obj), function (observable) {
if (observable && observable.value && !observable.value.nodeType && ko.isObservable(observable.value)) {
action(observable);
}
});
};
ko.extenders.trackChange = function (target, track) {
if (track) {
target.hasValueChanged = ko.observable(false);
target.hasDirtyProperties = ko.observable(false);
target.isDirty = ko.computed(function () {
return target.hasValueChanged() || target.hasDirtyProperties();
});
var unwrapped = target();
if ((typeof unwrapped == "object") && (unwrapped !== null)) {
traverseObservables(unwrapped, function (obj) {
applyChangeTrackingToObservable(obj.value);
obj.value.isDirty.subscribe(function (isdirty) {
if (isdirty) target.hasDirtyProperties(true);
});
});
}
target.originalValue = target();
target.subscribe(function (newValue) {
// use != not !== so numbers will equate naturally
target.hasValueChanged(newValue != target.originalValue);
target.hasValueChanged.valueHasMutated();
});
if (!target.getChanges) {
target.getChanges = function (newObject) {
var obj = target();
if ((typeof obj == "object") && (obj !== null)) {
if (target.hasValueChanged()) {
return ko.mapping.toJS(obj);
}
return getChangesFromModel(obj);
}
return target();
};
}
}
return target;
};
var applyChangeTrackingToObservable = function (observable) {
// Only apply to basic writeable observables
if (observable && !observable.nodeType && !observable.refresh && ko.isObservable(observable)) {
if (!observable.isDirty) observable.extend({ trackChange: true });
}
};
var applyChangeTracking = function (obj) {
var properties = getObjProperties(obj);
ko.utils.arrayForEach(properties, function (property) {
applyChangeTrackingToObservable(property.value);
});
};
var getChangesFromModel = function (obj) {
var changes = null;
var properties = getObjProperties(obj);
ko.utils.arrayForEach(properties, function (property) {
if (property.value != null && typeof property.value.isDirty != "undefined" && property.value.isDirty()) {
changes = changes || {};
changes[property.name] = property.value.getChanges();
}
});
return changes;
};
var viewModel = {
Name: ko.observable("Pete"),
Age: ko.observable(29),
Skills: ko.observable({
Tdd: ko.observable(true),
Knockout: ko.observable(true),
ChangeTracking: ko.observable(false),
Languages: ko.observable({
Csharp: ko.observable(false),
Javascript: ko.observable(false)
}),
}),
Occupation: ko.observable("Developer")
};
applyChangeTracking(viewModel);
viewModel.Skills().ChangeTracking(true);
viewModel.Skills().Languages({
Csharp: ko.observable(true),
Javascript: ko.observable(true),
});
console.log(getChangesFromModel(viewModel));
@jbailey4
Copy link

This is awesome, good work! I did have to make one change on line 42 for my use case, target.originalValue = target(); to target.originalValue = ko.toJS(target()); in order to get a copy of the original value and not a reference. This is more important with objects, because the new value in the subscription callback would be the same as the target.originalValue as they were both references to the same object.

@ryantheleach
Copy link

Your blog post hasn't been updated for the new username on gist.
https://roysvork.wordpress.com/2014/01/12/tracking-changes-to-complex-viewmodels-with-knockout-js/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment