The JsonRestStore is a Dojo Data store that provides a JSON-based RESTful interface to servers and implements the Dojo Data read, write, notification, and identity APIs.

One of the core concepts of a REST architecture is hyperlinking, and this is available in the JsonRestStore through it’s referencing support. This concept enables powerful features like lazy loading and cross-store referencing that greatly improve the effectiveness and usability of the JsonRestStore. All of the features described in this article are available with Dojo Toolkit version 1.2 and later.

Lazy Loading

The JsonRestStore utilizes JSON referencing, which is essentially hyperlinking for JSON and enables the communication of a variety of rich data structures. It also enables lazy loading, achieved by using a reference (a JSON hyperlink) to another object instead of actually including that object in the JSON structure. We can perform lazy loading by using references to objects when performing queries instead of returning full object representations. These references do not need to consist solely of the target identifier, but can also contain a partial set of the data that is being referenced.

References are designated with the use of the $ref property, and the target is resolved using standard relative URL resolution like HTML hyperlinks. For example, suppose we queried for all the purchase orders that are active, and we wanted to return just the shipToAddress and the active. This would be particularly useful if we wanted to display a small representation of the objects in a grid, and then show more info as desired. Let’s create a grid connected to a JsonRestStore:

// create the store
var poStore = new dojox.data.JsonRestStore({target:"/PO/"});
// create a layout
gridLayout = [
	{ name: 'Ship To Address', field: 'shipToAddress'},
	{ name: 'Active', field: 'active'}];
// create the grid and connect it to the store
var grid = new dojox.grid.DataGrid({
	store: poStore,
	structure: gridLayout
}, dojo.byId("gridElement"));
grid.startup();

And now we make a query through the grid:

grid.setQuery("?active=true");

Which would trigger:

GET /PO/?active=true

And the server could respond with partially loaded reference objects:

[
  {"$ref":"3433",
    "shipToAddress":"123 Main St",
    "active":true},
  {"$ref":"3437",
    "shipToAddress":"456 Wall St",
    "active":true},
]

We can display a grid with these two columns (purchaseDate and id) based on this single request/response. We could then provide more detail to the user in another form when a user clicks on a row:

dojo.connect(grid,"onRowClick",function(){
   var selectedPo = grid.selection.getSelected()[0];
   poStore.loadItem({
      item: selectedPo,
      onItem: function(loadedPo){
          // the loadedPo should contain a full representation of the PO object 
          // and the UI can be updated based on this object.
          dojo.query("input",form).forEach(function(input){
                // get the form elements, 
                // where each input name matches a property name
		input.value = poStore.getValue(loadedPo,input.name);
		dojo.connect(input,"onchange",function(){
			// update the item when input's are edited
			poStore.setValue(loadedPo,input.name,input.value);
		});


	});

      }
   });
});

When the user clicks on a row in the grid, this triggers the onRowClick event, and we can fully load the item from the server using the loadItem method. Once the item is loaded, we can use a simple for loop to populate form elements with the values from the object. For the first item in the grid (first object in the query response), the loadItem call will send this request to the server:

GET /PO/3433

And the server could respond with something like this:

{"shipToAddress","299 Chestnut Ridge",
  "active":true,
  "shippingMethod":"UPS",
  "customer":{"$ref":"/Customer/johndoe", "name": "John Doe"},
  "items":[{"quantity":1, "product":{"$ref":"/Product/sillyputty"}}],
  "purchaseDate":""2008-11-11T13:27:44Z",
  "id":"3433"}

Cross-store References

One thing you may have noticed about this last response from the server is that also includes references to other objects, but these objects are referenced by absolute paths. These references allow us to link to objects within a different namespace, which can be associated with a different store. If we are storing product information under the /Product/ URL, we could create another store for this data:

var productStore = new dojox.data.JsonRestStore({target:"/Product/"});

Now, when the client receives a response like the one above, the referenced product object will be resolved and can be loaded as an item of the productStore. For example:

productStore.loadItem({
    item: loadedPo.items[0].product,
    onItem: function(loadedProduct){
        // do something with the sillyputty product
    }
});

By defining references between objects from different stores, we can clearly define relationships between data from different domains in a way that naturally maps to JavaScript objects. In particular, this is a powerful way to express the links created by joining tables in relational databases.

Cross-site References

Not only can we reference objects from other stores, but just like hyperlinks, we can reference objects from other domains. For example, suppose we had a product object:

{
   "name": "Barbie",
   "price": 9.95,
   "specifications": {"$ref": "http://barbie.com/Specification/barbie"}
}

This object defines that the specifications property value can be loaded from the barbie.com site. Due to the same origin policies of the browser that prevent direct XHR access to this site, before we can load the item using loadItem(), we must first specify what cross-domain loading techniques are available for this target site by registering with the XHR plugin system. For example, if barbie.com supports the window.name protocol for secure data transfer, we can register this capability:

dojo.require("dojox.io.xhrWindowNamePlugin");
dojox.io.xhrWindowNamePlugin("http://barbie.com/", 
               dojox.io.xhrPlugins.fullHttpAdapter);

And now we can proceed to load the specifications object just as we would for a cross-store reference, and the window.name transport will be used to load the item:

productStore.loadItem({
   item: product.specifications,
   onItem: function(specifications){
      // do something with the specifications
   }
});

Defining Methods for Store Items

One of the convenient capabilities of the JsonRestStore is to define a prototype object for the items in the store to inherit from (delegate property lookup). This prototype object is defined by providing an object as the prototype property of the store’s schema. You can define methods on this object and they will be available on the items in the store. For example, we could setup the poStore such that a method that marks a purchase order as completed and sends the change to the server would be available on items:

var poStore = new dojox.data.JsonRestStore({target:"/PO", schema:{
   prototype:{
      completeOrder: function(){
         poStore.setValue(this, "completed", true);
         poStore.setValue(this, "active", false);
         poStore.save();
      }
   }
}});

Now, with a poStore item, we can simply call:

poStore.fetchItemByIdentity({
   identity:"3433",
   onItem:function(poItem){
      poItem.completeOrder();
   }
});

This technique can also be used to create Java-bean style getters and setters for items. For example:

var poStore = new dojox.data.JsonRestStore({target:"/PO", schema:{
   prototype:{
      getActive: function(){
         poStore.getValue(this, "active");
      },
      setActive: function(value){
         poStore.setValue(this, "active", value);
      }
   }
}});

And now getActive() and setActive(value) can be called as methods on items.

More

Custom Services

Some servers have configurations that do not allow for the handling of HTTP standard verbs PUT and DELETE. However, it is still possible to use the JsonRestStore. You can create a custom service to handle the update and delete operations. Maulin Shah has an excellent post describing how to do this (with some lazy loading examples as well).

Schema

JsonRestStore can also use JSON Schema validation for constraining the properties on items. This allows you to define required structure of data used by JsonRestStore and quickly reject invalid changes.

Conclusion

JsonRestStore’s support for referencing and prototype inheritance allows you to work with data on the client in a way that closely corresponds to the way data may actually be conceptually structured and modeled and/or stored on the back end. Lazy loading allows you to transfer data in an optimally efficient manner. Together these features contribute making JsonRestStore a more comprehensive data store for client/server interaction.