change tracking
by rniemeyer
HTML
<script src="http://rniemeyer.github.com/KnockMeOut/Scripts/knockout-1.3pre.js"></script>
<input type="checkbox" data-bind="checked: someFlag" />
<hr/>
<ul data-bind="foreach: items">
<li>
<input data-bind="value: id" />
<input data-bind="value: name" />
</li>
</ul>
<hr />
<table data-bind="visible: myTracker.changes().length">
<tr>
<th>Property</th>
<th>New Value</th>
<th>Source Object</th>
</tr>
<tbody data-bind="foreach: myTracker.changes">
<tr>
<td data-bind="text: property"></td>
<td data-bind="text: ko.toJSON(value)"></td>
<td data-bind="text: ko.toJSON(source)"></td>
</tr>
</tbody>
</table>
CSS
.val { font-weight: bold; }
td, th { margin: 2px; padding: 2px; border: solid 1px black; }
th { background-color: #333; color: #fff; }
JavaScript
function Item(id, name) {
this.id = ko.observable(id);
this.name = ko.observable(name);
}
var viewModel = {
items: ko.observableArray([
new Item(1, "one"),
new Item(2, "two"),
new Item(3, "three")
]),
someFlag: ko.observable(false)
};
ko.changeTracker = function(viewModel) {
var self = this;
self.changes = ko.observableArray();
//add this change to the array of changes
function onPropertyChanged(property, source) {
self.changes.push({ property: property, value: ko.toJS(source[property]), source: source });
}
//track a single change
function trackChange(prop, source) {
var value = source[prop];
if (ko.isObservable(value)) {
value.subscribe(function() {
onPropertyChanged(prop, source);
});
}
}
//expose a function to track changes for each property on an object
self.trackChanges = function(model) {
for (var prop in model) {
if (model.hasOwnProperty(prop)) {
trackChange(prop, model);
var underlying = ko.utils.unwrapObservable(model[prop]);
if (underlying instanceof Array) {
ko.utils.arrayForEach(underlying, function(item) {
self.trackChanges(item);
})
}
else if (typeof underlying === "object") {
self.trackChanges(underlying);
}
}
}
}
}
var myTracker = new ko.changeTracker();
myTracker.trackChanges(viewModel);
ko.applyBindings(viewModel);