AJAX Made Easy

Base provides a small suite of functions suitable for use in a RESTful design that significantly simplifies the process of performing routine AJAX operations. Each of these functions provides explicit mechanisms that eliminate virtually all of the boilerplate you'd normally find yourself writing. Table 4-1 summarizes the property values for args.

Table 4-1. Property values for args

Name

Type (Default)

Comment

url

String

("")

The base URL to direct the request.

content

Object

({})

Contains key/value pairs that are encoded in the most appropriate way for the particular transport being used. For example, they are serialized and appended onto the query string as name1=value2 for a GET request but are included as hidden form fields for the case of an IFRAME transport. Note that even though HTTP allows more than one field with the same name (multivalued fields), this is not possible to achieve via the content property because it is a hash.

timeout

Integer

(Infinity)

The number of milliseconds to wait for the response. If this time passes, then the error callback is executed. Only valid when sync is false.

form

DOMNode | String

The DOM node or id for a form that supplies the key/value pairs that are serialized and provide the query string for the request. (Each form value should have a name attribute that identifies it.)

preventCache

Boolean

(false)

If true, then a special dojo.preventCache parameter is sent in the request with a value that changes with each request (timestamp). Useful only with GET-type requests.

handleAs

String

("text")

Designates the type of the response data that is passed into the load handler. Acceptable values depend on the type of IO transport: "text", "json", "javascript", and "xml".

load

Function

The load function will be called on a successful response and should have the signature function(response, ioArgs) {/*...*/}.

error

Function

The error function will be called in an error case and should have the signature function(response, ioArgs) {/*...*/}.

handle

Function

A function that stands in for both load and error, and thus should be called regardless of whether the request is successful.

sync

Boolean

(false)

Whether to perform a synchronous request.

headers

Object

({})

Additional HTTP headers to include in the request.

postData

String

("")

Raw data to send in the body of a POST request. Only valid for use with rawXhrPost.

putData

String

("")

Raw data to send in the body of a PUT request. Only valid for use with rawXhrPut.

The RESTful XHR functions offered by the toolkit follow; as of Dojo version 1.1, each of these functions sets the X-Requested-With: XMLHttpRequest header to the server automatically. A discussion of the args parameter follows.

Tip

All of the XHR functions return a special Object called Deferred, which you'll learn more about in the next section. For now, just concentrate on the discussion at hand.

dojo.xhrGet(/*Object*/args)

Performs an XHR GET request.

dojo.xhrPost(/*Object*/args)

Performs an XHR POST request.

dojo.rawXhrPost(/*Object*/args)

Performs an XHR POST request and allows you to provide the raw data that should be included as the body of the POST.

dojo.xhrPut(/*Object*/args)

Performs an XHR PUT request.

dojo.rawXhrPut(/*Object*/args)

Performs an XHR PUT request and allows you to provide the raw data that should be included as the body of the PUT.

dojo.xhrDelete(/*Object*/args)

Performs an XHR DELETE request.

dojo.xhr(/*String*/ method, /*Object*/ args, /*Boolean?*/ hasBody)

A general purpose XHR function that allows you to define any arbitrary HTTP method to perform asynchronsously.

Although most of the items in the table are pretty straightforward, the arguments that are passed into the load and error functions bear mentioning. The first parameter, response, is what the server returns, and the value for handleAs specifies how the response should be interpreted. Although the default value is "text", specifying "json", for example, results in the response being cast into a JavaScript object so that the response value may be treated as such.

Tip

In the load and error functions, you should always return the response value. As you'll learn later in this chapter, all of the various input/output calls such as the XHR facilities return a type called a Deferred, and returning responses so that callbacks and error handlers can be chained together is an important aspect of interacting with Deferreds.

The second parameter, ioArgs, contains some information about the final arguments that were passed to the server in making the request. Although you may not need to use ioArgs very frequently, you may occasionally find it useful—especially in debugging situations. Table 4-2 describes the values you might see in ioArgs.

Table 4-2. Property values for ioArgs

Name

Type

Comment

args

Object

The original argument to the IO call.

xhr

XMLHttpRequest

The actual XMLHttpRequest object that was used for the request.

url

String

The final URL used for the call; often different than the one provided because it is fitted with query parameters, etc.

query

String

Defined only for non-GET requests, this value provides the query string parameters that were passed with the request.

handleAs

String

How the response should be interpreted.

XHR Examples

At an absolute minimum, the arguments for an XHR request should include the URL to retrieve along with the load function; however, it's usually a very good idea to include an error handler, so don't omit it unless there you're really sure you can't possibly need it. Here's an example:

//...snip...
dojo.addOnLoad(function(  ) {
    dojo.xhrGet({

        url : "someText.html",  //the relative URL

        // Run this function if the request is successful
        load : function(response, ioArgs) {
            console.log("successful xhrGet", response, ioArgs);

            //Set some element's content...
            dojo.byId("foo").innerHTML= response;

            return response; //always return the response back
        },

        // Run this function if the request is not successful
        error : function(response, ioArgs) {
            console.log("failed xhrGet", response, ioArgs);

            /* handle the error... */

            return response; //always return the response back
        }
    });
});
//...snip...

You may not necessarily want plain text back; you may want to time out the request after some duration, and you might want to pass in some additional information a query string. Fortunately, life doesn't get any harder. Just add some parameters, like so:

dojo.xhrGet({ 
    url : "someJSON.html", //Something like: {'bar':'baz'}

    handleAs : "json", //Convert to a JavaScript object 

    timeout: 5000, //Call the error handler if nothing after 5 seconds 

    content: {foo:'bar'}, //Append foo=bar to the query string 

    // Run this function if the request is successful 
    load : function(response, ioArgs) { 
        console.log("successful xhrGet", request, ioArgs); 
        console.log(response); 

        //Our handleAs value tells Dojo to 
        //convert the data to an object 

        dojo.byId("foo").innerHTML= response.bar; 
        //Display now updated to say 'baz' 

        return response; //always return the response back 
    }, 

    // Run this function if the request is not successful 
    error : function(response, ioArgs) { 
        console.log("failed xhrGet"); 
        return response; //always return the response back 
    } 
});

Do note that not specifying a proper value for handleAs can produce frustrating bugs that may not be immediately apparent. For example, if you were to mistakenly omit the handleAs parameter, but try to access the response value as a JavaScript object in your load function, you'd most certainly get a nasty error that might lead you to look in a lot of other places before realizing that you are trying to treat a String as an Object—which may not be immediately obvious because logs may display the values nearly identically.

Although applications tend to perform a lot of GET requests, you are bound to come across a circumstance when you'll need to PUT, POST, or DELETE something. The process is exactly the same with the minor caveats that you'll need to include a putData or postData argument for rawXhrPut and rawXhrPost requests, respectively, as a means of providing the data that should be sent to the server. Here's an example of a rawXhrPost:

dojo.rawXhrPost({
    url : "/place/to/post/some/raw/data",
    postData : "{foo : 'bar'}", //a JSON literal
    handleAs : "json",

    load : function(response, ioArgs) {
        /* Something interesting  happens here */
        return response;
    },

    error : function(response, ioArgs) {
        /* Better handle that error */
        return response;
    }
});

General Purpose XMLHttpRequest Calls

Dojo version 1.1 introduced a more general-purpose dojo.xhr function with the following signature:

dojo.xhr(/*String*/ method, /*Object*/ args, /*Boolean?*/ hasBody)

As it turns out, each of the XHR functions from this chapter are actually wrappers around this function. For example, dojo.xhrGet is really just the following wrapper:

dojo.xhrGet  = function(args) {
    return dojo.xhr("GET", args); //Always provide the method name in all caps!
}

Although you'll generally want to use the shortcuts presented in this section, the more general-purpose dojo.xhr function can be useful for some situations in which you need to programmatically configure XHR requests or for times when a wrapper isn't available. For example, to perform a HEAD request for which there isn't a wrapper, you could do the following:

dojo.xhr("HEAD", {
    url : "/foo/bar/baz",
    load : function(response, ioArgs) { /*...*/},
    error : function(response, ioArgs) { /*...*/}
});

Hitching Up Callbacks

Chapter 2 introduced hitch, a function that can be used to guarantee that functions are executed in context. One common place to use hitch is in conjunction with XHR callback functions because the context of the callback function is different from the context of the block that executed the callback function. The following block of code demonstrates the need for hitch by illustrating a common pattern, which aliases this to work around the issue of context in the callback:

//Suppose you have the following addOnLoad block, which could actually be any
//JavaScript Object
dojo.addOnLoad(function(  ) {

        //foo is bound the context of this anonymous function
        this.foo = "bar";

        //alias "this" so that it can be referenced inside of the load callback...
        var self=this;
        dojo.xhrGet({
            url : "./data",
            load : function(response, ioArgs) {
                //you must have aliased "this" to reference foo inside of here...
                console.log(self.foo, response);
            },
            error : function(response, ioArgs) {
                console.log("error", response, ioArgs);
            }
        });

});

While it may not look very confusing for this short example, it can get a bit messy to repeatedly alias this to another value that can be referenced. The next time you encounter the need to alias this, consider the following pattern that makes use of hitch :

dojo.addOnLoad(function(  ) {

        //foo is in the context of this anonymous function
        this.foo = "bar";

        //hitch a callback function to the current context so that foo
        //can be referenced
        var callback = dojo.hitch(this, function(response, ioArgs) {
            console.log("foo (in context) is", this.foo);
            //and you still have response and ioArgs at your disposal...
        });

        dojo.xhrGet({
            url : "./data",
            load : callback,
            error : function(response, ioArgs) {
                console.log("error", response, ioArgs);
            }
        });

});

And don't forget that hitch accepts arguments, so you could just as easily have passed in some parameters that would have been available in the callback, like so:

dojo.addOnLoad(function(  ) {

        //foo is in the context of this anonymous function
        this.foo = "bar";

        //hitch a callback function to the current context so that foo can be
        //referenced
        var callback = dojo.hitch(
            this,
            function(extraParam1, extraParam2, response, ioArgs) {
                console.log("foo (in context) is", this.foo);
                //and you still have response and ioArgs at your disposal...
            },
            "extra", "params"
         );

        dojo.xhrGet({
            url : "./data",
            load : callback,
            error : function(response, ioArgs) {
                console.log("error", response, ioArgs);
            }
        });

});

If you may have a variable number of extra parameters, you can instead opt to use arguments, remembering that the final two values will be response and ioArgs.

Get Dojo: The Definitive Guide now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.