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:
- Construct a standard Dojo XHR invocation structure containing the URL, arguments, and data format metadata.
- 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.
- Populate the putData/postData/deleteData/getData node of the invocation structure by translating the content node to a JSON representation.
- Call the XHR method with the altered XHR invocation structure.
- 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.
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.
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.
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");
}
}); |
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 Resources section.
| Description | Name | Size | Download method |
|---|---|---|---|
| EasyRestService code sample - project archive | EasyRestServiceExample.zip | 6.5KB | HTTP |
Information about download methods
Learn
- Read the DojoCampus documentation for JsonRestStore.
- Explore the Dojo API for JsonRestStore. JsonRestStore causes all saved
modifications to be sent to the server using REST commands (GET, PUT, POST, or
DELETE).
Get products and technologies
- Download IBM product
evaluation versions or explore
the online trials in the IBM SOA Sandbox and get your hands on
application development tools and middleware products from DB2®, Lotus®,
Rational®, Tivoli®, and WebSphere®.
Discuss
- Create your My developerWorks profile today and set up a watch list on Dojo and REST. Get connected and stay
connected with My developerWorks.
- Find other developerWorks members interested in web development.
- Share what you know: Join one of our developerWorks groups focused on web
topics.
- Roland Barcia talks about Web 2.0 and middleware in his blog.
- Follow developerWorks' members' shared bookmarks on web topics.
- Get answers quickly: Visit the Web 2.0 Apps forum.

Nick Maynard works for the IBM Software Solutions Transformation team in Hursley, UK. He specializes in Web programming, Linux, Web services, and business integration technologies. You can contact Nick at nick.maynard@uk.ibm.com.




