The XMLHTTP request object (XHR for short) is one of the basic building blocks for constructing responsive Ajax-drive interactions. By allowing you to retrieve data on the user's behalf without refreshing the whole page the XHR object provides tremendous, but cross-browser XHR usage is beset by memory leaks, divergent APIs, a lack of built-in form encoding from JavaScript, and painful corner cases when de-serializing response data.
Dojo provides a solid set of battle-tested XHR wrapper functions to allow you to build Ajax interactions with confidence, use a unified API, and handle forms with ease. These APIs are built into Dojo Base, so you can use them in any page that includes dojo.js. Read on to learn how easy it is to build powerful Ajax interactions with Dojo.
Many programming tutorials contain a "Hello, World!" example, so it seems appropriate to have one for Dojo XHR. In this example, your web page will fetch a snippet of content via XHR and attach it directly to your page.
To setup the example:
Create a file named ajax.txt. It's
your decision what to put in the file. "Hello, Ajax world!" is a good
start.
Put ajax.txt in
the default documents directory on your
web server. In many cases, that is the
httpdocs directory.
Open your web browser and navigate to the file. You
should see the contents of ajax.txt.
Create a file named hello.html. Copy
and paste the contents of Example 1, “Hello, Ajax world!” into that file.
Set the URL
argument to the value you used to test the server setup.
Put hello.html in
the default documents directory on your
web server. In many cases, this is the
httpdocs directory.
Put the file in the same directory as ajax.txt.
Make the file read-only.
Open your web browser and navigate to hello.html. You
should see the contents of ajax.txt in your browser window.
Example 1. Hello, Ajax world!
<html>
<head>
<title>Hello, Ajax world!</title>
<script type="text/javascript"
src="http://o.aolcdn.com/dojo/1.0.0/dojo/dojo.xd.js"></script> <!--➀-->
<script type="text/javascript">
function hello() { // ➁
dojo.xhrGet( { // ➂
// The following URL must match that used to test the server.
url: "http://server/ajax.txt",
handleAs: "text",
timeout: 5000, // Time in milliseconds
// The LOAD function will be called on a successful response.
load: function(response, ioArgs) { // ➃
dojo.byId("cargo").innerHTML = response; // ➄
return response; // ➅
},
// The ERROR function will be called in an error case.
error: function(response, ioArgs) { // ➃
console.error("HTTP status code: ", ioArgs.xhr.status); // ➆
return response; // ➅
}
});
}
</script>
<script type="text/javascript">
dojo.addOnLoad(hello); // ➇
</script>
</head>
<body>
<div id="cargo" style="font-size: big"></div> <!--➈-->
</body>
</html>
| ➀ |
This JavaScript program bootstraps Dojo 1.0 via the AOL Content Distribution Network. If you choose to install Dojo locally, use the following script tag to bootstrap Dojo: <script type="text/javascript" src=" Modify the directory reference in the SRC attribute to match the location of your Dojo installation |
| ➁ | This function will be called after Dojo completes its initialization phase. |
| ➂ | The desired HTTP method call (GET, POST, PUT, DELETE) is part of the function name. |
| ➃ | Inside this function, the this
variable will be the object used as the argument to the
dojo.xhrGet() call. |
| ➄ | This statement demonstrates one way of stuffing a
server response into a document. The
dojo.xhrGet call asks Dojo to treat
data from the server as text (handleAs: "text"). Therefore
response will be a text
string. |
| ➅ | Dojo recommends that you always
|
| ➆ |
ioArgs is an object with
some useful properties on it. For instance, for
XMLHttpRequest calls,
ioArgs.xhr is the
XMLHttpRequest that was used for the
call. |
| ➇ |
The Dojo team recommends that you always use
Use Two ways to abuse Dojo. The following techniques will produce tedious,
head-scratching debugging sessions by not using
|
| ➈ | Holding area for the server response. A common
use case is that the server returns an HTML fragment that
the client will want to display. The
innerHTML attribute of such
placeholder <div> tags is a convenient way to
display HTML fragments. |
JSON, or Javascript Object Notation, is a lightweight data interchange standard. It can, in theory, be used to pass data between any two programming languages, but it has special advantages when used with Javascript. JSON is fundementally just the Javascript array and object initializer syntax on its own. So this array of objects in a Javascript program:
/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}Roughly everything after the "=" is JSON. Here's what the JSON packet would look like coming back from a web service:
/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}"Excuse me," you might say, "but we already have a data interchange format. You might have heard of it ... it's called XML." So why not just pass it as:
/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}The answer is performance, performance, performance! JSON data is parsed up to 100 times faster than XML. That makes sense because the parser is just eval(). For small portions of data, this doesn't mean much, but for large ones it's indispensible. XML expressed in Javascript must carry the full weight of XML - including namespaces, DTD's and schemas. Furthermore, the DOM representation of XML is much more memory-intensive than native Javascript.
Add to that the fact that XML is interpreted a little differently in each browser, and JSON is the preferred method for data interchange in dojo. XML is supported, but you might not want to use it in simple scenarios. Where it really begins to make sense, however, is when large scale transformations are needed on the client side. Almost every modern browser today provides a client-side XSLT transform facility which can often outstrip JSON for speed in transforming large data sets from one structure to another since that transformation is handled in C or C++ and not in JavaScript. Whether your app chooses to use JSON or XML is often a foregone conclusion, but be aware that there are tradeoffs for each.
Provided your web service sends JSON (as the above example shows) Dojo pretty much handles the rest for you, including parsing the JSON into a JavaScript object. All you have to do is specify a JSON content handler:
/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}Regular web requests and Ajax requests with dojo.xhrGet/Post are much alike. Both use URL's and both use the HTTP protocol. But with browser requests, it is always clear to user when something goes wrong. You may get a 404 - Page Not Found, or a Server Unavailable, or at least something that says "Error". Ajax requests happen in the background, so when they error out the user won't know. Even worse, if the response never comes the browser may appear to lock up.
That's why it's extremely important to provide an error handler and a timeout handler with any dojo.xhrGet/Post calls. You should consider these as critical as URL or the load function
At the very least, you should alert the user that something went wrong. Here's an example:
dojo.xhrGet({
url: "/cgi-bin/timeout.cgi",
load: function(data){
document.myForm.myBox.value = data;
dojo.byId("boxLoadTime").innerHTML = new Date();
},
error: function(err){
console.debug("Holy Bomb Box, Batman! An error occurred: ", err);
},
timeout: 2000
});
The error() function takes the same arguments that load() does. But unlike load(), the only useful parameter is data, which contains the error message. You can also find out what kind of error was generated by looking at the error object's "dojoType" property. It will usually be "timeout" or "cancel", but other error types are possible.
The timeout, given in milliseconds, defaults to 0, which means "wait forever". Even if you expect the request will take a long time, you should set a high value here (e.g. 15000 = 15 seconds), not 0.
The Url of dojo.xhrGet may contain parameters, like so:
url: 'myprogram.php?firstname=Chicken&lastname=Little&key=111111'
There are two problems: (1) it's difficult to URL encode everything, (2) it doesn't allow for dynamic parameters. The above works fine for everyone named Chicken Little, but ...
It's easier and more flexible to send an entire form of data. And you can do that with the form parameter of dojo.xhrGet
/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}Dojo provides low level I/O out of the box, but as applications grow in complexity it's natural to want something less one-off-ish. Dojo's Remote Procedure Calls (RPC) module aims to make this process less error prone, easier, and require less code.
Remote Procedure Calls allows you to invoke a method on a remote host. Dojo provides a basic RPC client class that has been extended to provide access to JSON-RPC services and Yahoo services. It was designed so that it is easy to implement custom RPC services.
For example, you have a little application that you want to use to make some server calls. For simplicity, the methods you want the server to do are add(x,y) and subtract(x,y). Without using anything special, like an RPC client, you could do something like this:
/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}This isn't particularly difficult but it is repetitive and error prone. Dojo's RPC clients simplify this process by taking a simple definition of the remote methods and application needs and generating client side functions to call these methods. You need only write this definition, and initialize an RPC client object and then all of these remote methods are available for you to use as normal.
The definition file, called a Simple Method Description (SMD) file, is a simple JSON string that defines a URL to process the RPC requests, any methods available at that URL, and the parameters those methods take. The definition for the example above might look like this:
/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}Once the definition has been created, the code is pretty simple. The definition can be supplied either as a URL to retrieve it, a JSON string, or a JavaScript object, as shown in the following example.
/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}Thats it! Now all that's left is to call the method as shown in the following example.
/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .javascript .imp {font-weight: bold; color: red;} .javascript .kw1 {color: #000066; font-weight: bold;} .javascript .kw2 {color: #003366; font-weight: bold;} .javascript .kw3 {color: #000066;} .javascript .co1 {color: #009900; font-style: italic;} .javascript .coMULTI {color: #009900; font-style: italic;} .javascript .es0 {color: #000099; font-weight: bold;} .javascript .br0 {color: #66cc66;} .javascript .st0 {color: #3366CC;} .javascript .nu0 {color: #CC0000;} .javascript .me1 {color: #006600;} .javascript .re0 {color: #0066FF;}myObject.add(3,5);
Of course this just calls the method and doesn't address what is returned from the call or how to get at the results. Just like the rest of the I/O system in dojo 0.9+, the RPC system returns a deferred to which you can attach callbacks.
/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}Or, it could be more succinctly done this way:
/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}You just add myCallbackMethod as a callback for the Deferred returned from myObject.add(). In this case myCallbackMethod is called with a parameter with a value of 8. Likewise, you can attach an errback method to the deferred object to process any errors returned from the server. You can add as many callbacks and errbacks to your deferred object as you want and they will be called in the order that they were connected to the deferred object.
In addition to JsonService, Dojo offers a JsonpService client, which is used for services such as Yahoo which provide JSON-P style service. Most services of this style can be represented in an SMD and passed to the JsonpService and used in the same fashion as above in a crossplatform manner. This makes mashups a cinch. Take a look in the dojox project at the Yahoo.smd example provide. Pass that url to JsonPService and all of Yahoo's services are at your finger tips and others are easy to add.
/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}The resulting Yahoo Service object allows you to perform web searches, do term extraction, and search all sorts of local an contextual data. See the SMD file for details on the available methods, parameters, and documentation pointers.
While Dojo is currently limited to these two RPC clients, the design of the base classes allows you to easily customize and extend dojo.rpc.RpcService to create services that meet your specific needs. Have a look at the source for RpcService.js and JsonService.js to see just how simple it is. If anyone is interested in contributing their own SMD files for their pet service or their company's services, please let us know!