Contents


Use Dojo's JsonRestStore with your REST services

Learn a simple, generic service implementation

Comments

Dojo's data abstraction layer, dojo.data, provides a standard API for Dojo applications to access a variety of back-end services. JsonRestStore, located in the Dojo extensions library DojoX, lets you quickly wire your applications to back-end Representational State Transfer (REST) services.

JsonRestStore is a good solution for linking Dojo to REST services. By default, though, JsonRestStore has expectations for the format of the information passed to, and returned by, the service. The Service Mapping Description (SMD) facility for customizing these interactions is, at best, difficult to understand.

When Dojo application authors need to communicate with a preexisting back-end REST store, when the store's communications cannot be changed, they use a custom dojox.rpc.Service implementation.

In this article, learn a simple, generic service implementation for wiring up nonstandard REST services to JsonRestStore. Explore how to use and extend it for your own services by walking through a practical example.

To download the source code for this article, see the Download section.

Example service implementation

EasyRestService provides implementations for the four REST operations: POST (create), GET (read), PUT (update), and DELETE (delete). For each activity, it provides the ability to hook in before and after invocation of the service method. The invocation life cycle is:

  1. Construct a standard Dojo XHR invocation structure containing the URL, arguments, and data format metadata.
  2. Call the arguments transformer for the method. This method can alter the invocation structure (including the path), and can be used to transform the invocation structure to conform with the REST service's expectations.
  3. Populate the putData/postData/deleteData/getData node of the invocation structure by translating the content node to a JSON representation.
  4. Call the XHR method with the altered XHR invocation structure.
  5. Add a results transformer to the XHR callback. This method can alter the data structure of the results. Use it to transform the results into the structure expected by JsonRestStore.

Listing 1 shows the example code.

Listing 1. EasyRestService source code source
dojo.provide("com.ibm.developerworks.EasyRestService");

(function() {
  var pa = com.ibm.developerworks.EasyRestService = function (path, serviceImpl, schema) {
    // Enforce the dojox.rpc.Rest trailing slash functionality
    path = path.match(/\/$/) ? path : (path + '/');
    
    // A dojox.rpc.Service implementation is a function with 3 function members
    var service;
    // GET function
    service = function(id, args) {
      return _execXhr("get", id, args);
    };
    // POST function member
    service['post'] = function(id, value) {
      return _execXhr("post", id, value);
    };
    // PUT function member
    service['put'] = function(id, value) {
      return _execXhr("put", id, value);
    };
    // DELETE function member
    service['delete'] = function(id) {
      return _execXhr("delete", id);
    };
    
    // Generic XHR function for all methods
    var _execXhr = function(method, id, content) {
      // Transform the method string
      var methodCapitalised = method.substring(0,1).toUpperCase() 
        + method.substring(1).toLowerCase();
      var methodUpperCase = method.toUpperCase();
      var methodLowerCase = method.toLowerCase();
      
      // Get the transformer functions
      var argumentsTransformer = service["transform" + methodCapitalised + "Arguments"];
      var resultTransformer = service["transform" + methodCapitalised + "Results"];
      
      // Construct the standard query
      var serviceArgs = {
        url : path + (dojo.isObject(id) ? '?' + dojo.objectToQuery(id) : 
          (id == null ? "" : id)), 
        handleAs : "json",
        contentType : "application/json",
        sync : false,
        headers : { Accept : "application/json,application/javascript" }
      };
      
      // Transform the arguments
      // NOTE: argumentsTransformer has a reference to "service"
      serviceArgs = argumentsTransformer(serviceArgs, arguments);

      // Copy the content into the appropriate *Data arg
      // getData, putData, postData, deleteData
      // NOTE: If you want your arguments transformer to edit the *Data arg directly, 
      // move the arguments transformer invocation to after this call 
      serviceArgs[methodLowerCase + 'Data'] = content;
            
      // Kick off the call
      var xhrFunction = dojo['xhr' + methodCapitalised];
      var deferred = xhrFunction(serviceArgs);
      // Add our result transformer
      // NOTE: resultTransformer has a reference to "service" too
      deferred.addCallback(dojo.partial(resultTransformer, deferred));
      
      return deferred;
    };

    // Mix in the service hooks
    // Uses a "default" implementation that does nothing
    // Service hooks will have a reference to the "service" object in their context
    dojo.mixin(service, 
      new com.ibm.developerworks.EasyRestService.DefaultHooks(), 
      serviceImpl);
    
    // Now remove any default _constructor() methods
    // This is necessary as the JsonRestStore stack uses _constructor() differently
    delete service['_constructor'];
    // Remove the declaredClass member if it has been added
    delete service['declaredClass'];
    
    // Save the path away
    service.servicePath = path;
    // Save the schema
    service._schema = schema;
    
    return service;
  };
})();

dojo.declare("com.ibm.developerworks.EasyRestService.DefaultHooks", null, {
  transformGetArguments: function(serviceArgs) {
    // Alter serviceArgs to provide the information the backend
    // service requires
    return serviceArgs;
  },
  transformPutArguments: function(serviceArgs) {
    // Alter serviceArgs to provide the information the backend
    // service requires
    return serviceArgs;
  },
  transformPostArguments: function(serviceArgs) {
    // Alter serviceArgs to provide the information the backend
    // service requires
    return serviceArgs;
  },
  transformDeleteArguments: function(serviceArgs) {
    // Alter serviceArgs to provide the information the backend
    // service requires
    return serviceArgs;
  },
  transformGetResults: function(deferred, results) {
    /*
     * JsonRestStore expects the following format:
     * [
     *  { id: "1", ... },
     *  { id: "2", ... },
     *  ...
     * ] 
     */
    return results;
  },
  transformPutResults: function(deferred, results) {
    /*
     * JsonRestStore does not expect any specific content here
     */
    return results;
  },
  transformPostResults: function(deferred, results) {
    /*
     * JsonRestStore expects:
     * 1) A "Location" response header with location of the new item.
     * 		From the Dojo API:
     * 			The server’s response includes a Location header
     * 			that indicates the id of the newly created object.
     * 			This id will be used for subsequent PUT and DELETE 
     * 			requests. JsonRestStore also includes a 
     * 			Content-Location header that indicates the temporary
     * 			randomly generated id used by client, and this 
     * 			location is used for subsequent PUT/DELETEs if no 
     * 			Location header is provided by the server or if 
     * 			a modification is sent prior to receiving a response 
     * 			from the server.
     *    NB: There is no JS method for altering response headers.  
     *      You might wish to try overriding the 
     *      deferred.ioArgs.xhr.getResponseHeader() method with your
     *      own implementation.
     * 2) The new item in the following format:
     * { id: "1", ... }
     */
    return results;
  },
  transformDeleteResults: function(deferred, results) {
    /*
     * JsonRestStore does not expect any specific content here
     */
    return results;
  }
});

The code in Listing 1 replicates the default functions of dojox.rpc.Rest. It can be used with JsonRestStore, as shown in Listing 2.

Listing 2. Default use of EasyRestService with JsonRestStore
dojo.require("com.ibm.developerworks.EasyRestService");
dojo.require("dojox.data.JsonRestStore");

var store = new dojox.data.JsonRestStore({
  service: new com.ibm.developerworks.EasyRestService("https://mydomain.com/restservice"),
  idAttribute : "id"
});

By default, an EasyRestService instance does not alter the arguments and results in any way. Instead of altering DefaultHooks to perform required arguments and results transformation, EasyRestService provides a mechanism for overriding these transformers on a per-instance basis.

Customizing EasyRestService

EasyRestService provides a simple mechanism for the provision of custom transformers. The example in Listing 3 alters the behavior of EasyRestService to log the GET invocation structure before execution.

Listing 3. Customizing EasyRestService
dojo.require("com.ibm.developerworks.EasyRestService");

var transformers = { 
  transformGetArguments: function(args) { 
    console.log(args); 
    return args; 
  }
};
var service = new com.ibm.developerworks.EasyRestService(
  "https://mydomain.com/restservice", transformers);

Similarly, all transformers in DefaultHooks can be overridden on a per-instance basis.

Example

Create two instances of EasyRestService: one for a compliant service and one for a noncompliant service. The example uses them as the service providers for two instances of JsonRestStore, and issues a basic fetch against the stores.

The data stores

In Listing 4 and Listing 5, the services are mocked up using two read-only files containing JSON structures.

Listing 4. Compliant REST service, compliantService.json
[
	{ id: 1, name: "Phil" },
	{ id: 2, name: "John" }
]
Listing 5. Noncompliant REST service, noncompliantService.json
{ items : [
	{ id: 1, name: "Phil" },
	{ id: 2, name: "John" }
] }

Store and service interaction code

JavaScript will instantiate and query the stores using the code in Listing 6.

Listing 6. JavaScript code to interact with the stores
// Create a store using a service that needs no transformations
compliantStore = new dojox.data.JsonRestStore({
  service : new com.ibm.developerworks.EasyRestService(
      "./compliantService.json"),
  idAttribute : "id"
});

// Cause an async fetch from the compliant service
dojo.create("p", {
  innerHTML : "Requesting from compliant service"
}, dojo.body(), "last");
compliantStore.fetch({
  onComplete : function(items, request) {
    console.log(items);
    // Log the number of items fetched
    dojo.create("p", {
      innerHTML : "Got " + items.length + " items from compliant service."
    }, dojo.body(), "last");
  }
});

// Create a store using a service which needs transformations
// to interpret the results
noncompliantStore = new dojox.data.JsonRestStore({
  service : new com.ibm.developerworks.EasyRestService(
      "./noncompliantService.json", {
        transformGetResults : function(deferred, results) {
          // This store wraps its results in an items object
          // so return the items object
          return results.items;
        }
      }),
  idAttribute : "id"
});

// Cause an async fetch from the noncompliant service
dojo.create("p", {
  innerHTML : "Requesting from noncompliant service"
}, dojo.body(), "last");
noncompliantStore.fetch({
  onComplete : function(items, request) {
    console.log(items);
 	// Log the number of items fetched
    dojo.create("p", {
      innerHTML : "Got " + items.length
          + " items from noncompliant service."
    }, dojo.body(), "last");
  }
});

Summary

In this article, you learned how to wire up your own REST services to JsonRestStore. The examples showed a simple method of transforming your service interface to provide the signatures that JsonRestStore requires. For information on the full data structure expected by JsonRestStore, you should refer to the comments in DefaultHooks, the JsonRestStore documentation at DojoCampus, and the API documentation.

For information on the full data structure expected by JsonRestStore, see the Related topics section.


Downloadable resources


Related topic

  • Download IBM product evaluation versions and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.

Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development, Java development
ArticleID=600700
ArticleTitle=Use Dojo's JsonRestStore with your REST services
publish-date=12142010