As of version 1.6, dojo comes with the new Dojo Object Store API. This is an awesome thing, as it greatly simplifies the work with data stores in Dojo. Everybody who had to do with the traditional dojo.data API felt it was overly complex and hard to use – this has finally changed now. There are also wrappers from and to the old and new APIs, so that you can do stuff like using your traditional data-aware widgets with a new Object Store. And the goodness doesn’t end here; but more on this later. If you haven’t done so yet, you might want to read the excellent post on the new Dojo Object Stores by Kris Zyp where he explains all the awesomeness he created.

Dojo also comes with two fresh implementations for the API, a non-persistent memory store and a JsonRest store that interacts with a server through RESTful HTTP requests. You can also observe changes made to objects in the store. What is missing is a store that uses a client-side persistent backend, which would be useful for a couple of reasons (e.g., as one commenter in that post asks, to use it as chache store for dojo.store.Cache). As Kris already mentions, it’s a piece of cake to create one, so let’s go ahead and do it!

Choosing the Backend

The post mentions two possible candidates: dojox.storage and StorageJS, both already having a similar API, so making them compliant to the Dojo Object Store API is fairly easy. For this tutorial I choose StorageJS – because it is very lightweight and still provides all we need (and, well, for a couple of other reasons that don’t belong in this post). So, what is the exact API we need to comply to? Check it out in the docs. See the first sentence in that paragraph? “Every method in the API is optional, it’s presence indicating support for that feature.” Wow, that’s nice!

Basic compliance

Let’s start with the methods get(), put(), add() and remove(). That will make our store pretty usable for most store scenarios.

First, create a ‘store’ object we can work with (StorageJS creates a global variable called storage where all it’s methods reside):

var store = dojo.delegate(storage);

‘store’ now contains everything that ‘storage’ does, but we can leave the original ‘storage’ object alone and modify our ‘store’ object if needed. Time for the first method, let’s start with get(). StorageJS works with Strings as keys and values only, so we need to parse what we get from it.

store.get = function(id){
    // Need to parse the string we get from backend:
    return dojo.fromJson(storage.get(id));
};

Easy one, now move on to put(). The Object Store API allows user to do stuff like this: store.put({foo: 'bar'}, {id: 3}) as well as store.put({id: 3, foo: 'bar'}). Also, StorageJS only has the method set() instead of put() and add().

store.put = function(object, options){
    // Get the id, wherever it comes from
    var id = options && options.id || object.id;

    // Stringify data -- remember, StorageJS is a key/value backend wrapper.
    var data = dojo.toJson(object);

    // Now save the data away
    storage.set(id, data);
};

Easy, as well – you could write this as a one-liner. Now, add(). It’s close to put, except that it won’t overwrite data that already exists. So we just need to check if something with the given id already exists and throw an error if so.

store.add = function(object, options){
    // Get the id
    var id = options && options.id || object.id;

    // Check if something exists under the given
    // id -- StorageJS always returns null when
    // there's no data for a certain key.
    if(storage.get(id) !== null){
        throw new Error("Object already exists");
    }

    // We're save to go now! So hand over the
    // request to the put() method.
    store.put(object, options);
};

What about remove()? Nothing to do there, it already has the desired signature and functionality.

That’s it, we’re done with basic compliance. Our ‘store’ object now implements the basic methods of the Dojo Object Store API and can be used by anything that can work with a Dojo Object Store – and it will keep it’s state persistent in the browser.

More compliance? Querying!

There’s one interesting thing in the API: the query() method. Uh, yeah, we want that! Lucky us, Dojo already provides a query engine as well as a method that makes sure there are iterative methods and a total property containing the number of hits available in our result (well, it also abstracts away the differences between sync and async results, but as StorageJS is purely synchronous, this is not important to us). First, define a queryEngine:

// Let's take the provided queryEngine :)
store.queryEngine = dojo.store.util.SimpleQueryEngine;

So, if we want our store to be able to run queries, all we need to do is to take the store’s data and hand it over to the query engine. We use StorageJS’ getAll() method to acquire the data. It will return an array of objects in the form { key: ‘theKey’, value: ‘theValue’}, but the queryEngine wants an array containing only the objects, so we need to do a conversion.

store.query = function(query, options){
    // Get all data from the backend
    var rawData = storage.getAll();

    // And convert it into a format that the
    // queryEngine can work with
    var data = [];
    for(var i = 0, l = rawData.length; i < l; i++){
        var item = rawData[i];
        var dataObject = dojo.fromJson(item.value);
        data.push(dataObject);
    }

    // Now call the queryEngine on the data
    var results = this.queryEngine(query, options)(data);

    // And return the result, after it has been
    // enhanced.
    return dojo.store.util.QueryResults(results);
}

Now we can query our store for specific data, like so: var results = store.query({ prime: true }); This will give us all objects in the store that have a property called ‘prime’ and have that property set to true.

Hierarchie and Transactions

The API also mentions methods about data hierarchie and transactions, but I’m not going to cover these in this tutorial.

Convenience and Optimization

Currently, every time we run a query, we fetch all data again from our storage engine. So, if you don’t have a massive amount of data and want to query the store a lot, it might be wise to maintain a copy of all data in memory. There are other things you could optimize as well – or you could write your own queryEngine.

For your convenience,  here’s a wrapper that creates a Dojo Object Store like we did above and accepts a useMemory parameter during instantiation that denotes whether a copy of the data should be kept in memory or not. It uses the traditional require/provide instead of the AMD format. If you want to use it, make sure you use the right namespace in the declare() call! (Or, not recommended, copy it in the /dojo/store directory.)

If you want to fool around with it in the console first, you can go here (non-memory-using) or here (memory-using), check out the source and fire up the console and try things out.

Getting StorageJS

The above code uses the features base and getAll, so you could do a specific build of StorageJS with these features or just grab a full build of it for your desired storage engine (the storage-full-[engine].js files). If you don’t know about storage engines, all modern browser support localStorage, so this is the one what you might want.