Blog Archives

Simple client storage for view models with AmplifyJS and Knockout

From time to time, for a variety of different reasons, it can be desirable to use client storage to store data. There are many ways to utilize client storage but AmplifyJS is a very neat library that provides a consistent API to handle client storage that works in most browsers. This post explores a handy technique for utilizing client storage for knockoutjs based view models.

Why would I ever use client storage?

clientstorageexampleThe canonical example of this is using client storage to improve user experience, remembering user preferences or previously entered values such that the user doesn’t have to start all over.

A good example of client storage being applied can be found in the tutorials for knockoutjs. User progress in the tutorial is “remembered” and the option to restore is given when revisiting the site at a later time. The knockout tutorial site uses AmplifyJs under the hood to record user progress into client storage.

AmplifyJs

It’s pretty easy to use AmplifyJs on your site. After adding the appropriate script references, you can use amplify like this:

var item = { foo: "bar" };

amplify.store( "storeExample1", item );

The value “storeExample1” is the key that the item is stored against, and can be used to later retrieve the value as follows:

var item = amplify.store( "storeExample1" );

Now we’ve seen how easy it is to store and retrieve values using AmplifyJS, we can use this API for storing user data when it changes, and restore the value on coming back to the page at a later time. This can sometimes be easier said than done though; we have to remember to call amplify to store new values of the data whenever it changes.

Knockout to the rescue

One of knockout’s primary components, the observable, notifies subscribers of changes to it’s underlying data. It’s counterpart, the computed observable, is a function that is dependent on one or more other observables – reevaluating every time any of these dependencies change. This allows for us to easily call amplify to store data each and every time it is updated:

var target = ko.observable(amplify.store( "key" )); //populate from amplify

ko.computed( function() {

      amplify.store( "key", target()); //store new value on every change

});

target("some new value"); //setting the new value, triggering computed observable

Using this technique, we can keep our client-storage up-to-date on every change to an observable property.

One unfortunate side-effect of using this approach is that we now have to write a computed observable for every observable property we wish to store in client storage. Fortunately we can apply another knockout technique to keep our codebase DRY.

Extending Observables

Knockout includes a neat extension point that allows developers to easily augment knockout observables with additional functionality – extenders.

Applying this technique to our observable gives us a tidy way to apply client storage to any observable property:

var target = ko.observable( "default value" ).extend( { localStore: "key" } );

Any changes made to the observable value will be stored using amplify, and will be restored into the observable value when returning to the site at a later time.

The extender shown can be implemented as follows:

ko.extenders.localStore = function (target, key) {

    var value = amplify.store(key) || target();

      

    var result = ko.computed({

        read: target,

        write: function(newValue) {

            amplify.store(key, newValue);

            target(newValue);

        }

    });

 

    result(value);

 

    return result;

};

Here is a fiddle that demonstrates the localStore extender in action: http://jsfiddle.net/craigcav/QSCsK/