Lately I’ve been using Knockout JS to build MVVM applications. I wanted to add some form of “offline” caching to my Model. I decided to use Amplify JS to add the model to LocalStorage.

After an extensive Google search I found a script that leverages both systems to store a single observable. The interesting thing is that it uses Knockout’s subscribe method to subscribe to changes. This means all changes are ‘automatically’ stored away. I’ve decided to extend Knockout’s ko variable:

ko.trackChange = function (observable, key) {

	var store = amplify.store.localStorage;

	//initialize from stored value, or if no value is stored yet, 
	//use the current value
	var value = store(key) || observable();

	//track the changes
	observable.subscribe(function (newValue) {
	    store(key, newValue || null);

	    if (ko.toJSON(observable()) != ko.toJSON(newValue)) {
	        observable(newValue);
	    }
	});

	observable(value); //restore current value
};

The next step is to make a function that adds persistence to all the observables of a given view model. Check the following code:

ko.persistChanges = function (vm, prefix) {

    if (prefix === undefined) {
        prefix = '';
    }

    for (var n in vm) {

        var observable = vm[n];
        var key = prefix + n;

        if (ko.isObservable(observable) && !ko.isComputed(observable)) {

            //track change of observable
            ko.trackChange(observable, key);

            //force load
            observable();
        }
    }
};

ko.isComputed = function (instance) {
	if ((instance === null) || (instance === undefined) || (instance.__ko_proto__ === undefined)) {
		return false;
	}

	if (instance.__ko_proto__ === ko.dependentObservable) {
		return true;
	}

	return ko.isComputed(instance.__ko_proto__); // Walk the prototype chain
};

It basically looks through the given model vm and stores all non-computed observables. To prevent collisions I’ve also added a prefix. Hooking this up is fairly easy. The following example shows how it works:

//model specification
function SimpleModel() {
    var _this = this;

    this.message = ko.observable('Hello');
    this.subject = ko.observable('World');
    this.text = ko.computed(function () {
        return _this.message() + ' ' + _this.subject() + '!';
    });
}

//new it up
var vm = new SimpleModel();

//bind to interface
ko.applyBindings(vm);

//persist it
ko.persistChanges(vm, 'vm-');

//alert 1 - should be 'Hello World!'
//at least the first time 😉
alert(vm.text());

//change it
vm.message('Bye');

//alert 2 - should be 'Bye World!'
alert(vm.text());

//load a new one up to check
var vm2 = new SimpleModel();
ko.persistChanges(vm2, 'vm-');

//alert 3 - should be 'Bye World!'
alert(vm2.text());

Check this JSFiddle to fiddle around with the code. Let me know what you think.