CHAPTER 15

image

Using Ajax: Part II

In this chapter, I show you how to use the low-level jQuery Ajax API (application programming interface). The term low level implies rooting around in the guts of the request, but that really isn’t the case. The methods I describe in this chapter are not as convenient as those in Chapter 14, but with a little more effort you can configure the fine detail of the request when the configuration used by the shorthand and convenience methods doesn’t quite do the job. Table 15-1 provides the summary for this chapter.

Table 15-1. Chapter Summary

Problem Solution Listing
Make an Ajax call with the low-level API Use the ajax method 1
Get details of the request in a way that is similar to the native XMLHttpRequest object Use the jqXHR method 2
Specify the URL for an Ajax request Use the url setting 3
Specify the HTTP method for a request Use the type setting 4
Respond to successful requests Use the success setting 5
Respond to unsuccessful requests Use the error setting 6
Respond to completed requests, regardless of success or errors Use the complete setting 7, 8
Configure a request before it is sent Use the beforeSend setting 9
Specify multiple functions to handle successful, unsuccessful, or completed requests Specify an array of functions for the success, error, or complete setting 10
Specify the element that will be assigned to the this variable in the functions for the success, error, and complete settings Use the context setting 11
Respond to events for all Ajax requests Use the global event methods 12
Specify whether a request will lead to global events being triggered Use the global setting 13
Set the timeout for a request Use the timeout setting 14
Add headers to the request Use the headers setting 14
Specify the content type being set to the server Use the contentType header 15
Specify whether a request will be performed synchronously or asynchronously Use the async setting 16
Ignore data that has not been changed Use the ifModified setting 17
Respond to the HTTP status code sent by the server Use the statusCode setting 18
Clean up the response data Use the dataFilter setting 19
Control how data is converted Use the converters setting 20
Define a common configuration for all Ajax requests Use the ajaxSetup method 21
Dynamically change the configuration for individual requests Use the ajaxPrefilter method 22

JQUERY CHANGES SINCE THE LAST EDITION

As of jQuery 1.9/2.0, the methods that set up handlers for the Ajax global events can only be called on the document object (in earlier jQuery versions, these methods could be used on any element). See the section “Using the Global Ajax Events” for the details of these methods.

Making a Simple Ajax Request with the Low-Level API

Making a request with the low-level API isn’t much more complicated than using the shorthand and convenience methods I showed you in Chapter 14. The difference is that you can configure many different aspects of the request and get a lot more information about the request as it is performed. The method that is at the heart of the low-level API is ajax, and Listing 15-1 provides a simple demonstration of its use.

Listing 15-1.  Using the ajax Method

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="handlebars.js" type="text/javascript"></script>
    <script src="handlebars-jquery.js" type="text/javascript"></script>
    <script src="jquery.validate.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <script id="flowerTmpl" type="text/x-handlebars-template">
        {{#flowers}}
        <div class="dcell">
            <img src="{{product}}.png"/>
            <label for="{{product}}">{{name}}:</label>
            <input name="{{product}}" data-price="{{price}}" data-stock="{{stocklevel}}"
                value="0" required />
        </div>
        {{/flowers}}
    </script>
    <script type="text/javascript">
        $(document).ready(function () {
            $.ajax("mydata.json", {
                success: function (data) {
                    var tmplElems = $("#flowerTmpl")
                        .template({flowers: data}).filter("*");
                    tmplElems.slice(0, 3).appendTo("#row1");
                    tmplElems.slice(3).appendTo("#row2");
                }
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
        <div id="oblock">
            <div class="dtable">
                <div id="row1" class="drow">
                </div>
                <div id="row2"class="drow">
                </div>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

You use the ajax method by passing the URL that you want to request and a map object whose properties define a set of key/value pairs, each of which configures a setting for the request.

image Note  This chapter relies on the same Node.js script used in Chapter 14.

In this example, my map object has one property—success—which specifies the function to call if the request is successful. I request the mydata.json file from the server and use it with a data template to create and insert elements into the document, just as I did in the previous chapter with the shorthand methods. By default, the ajax method makes an HTTP get request, which means that the example is equivalent to using the get or getJSON method, which I showed you in Chapter 14. (I’ll show you how to create POST requests later in the section “Making a POST Request.”)

Many settings are available and I explain them throughout the rest of the chapter, along with methods that jQuery provides to make Ajax easier to use.

Understanding the jqXHR Object

The result returned by the ajax method is a jqXHR object that you can use to get details about and interact with the Ajax request. The jqXHR object is a superset of the XMLHttpRequest object that is defined as part of the World Wide Web Consortium (W3C) standard that underpins browser support for Ajax, adapted to work with the jQuery deferred object features that I describe in Chapter 35.

You can simply ignore the jqXHR object for most Ajax requests, which is exactly what I suggest you do. The jqXHR object is useful when you need more information about the response from the server than would otherwise be available. Table 15-2 describes the members of the jqXHR object.

Table 15-2. The jqXHR Members

Member Description
readyState Returns the progress of the request through its life cycle from unsent (value 0) to complete (value 4)
status Returns the HTTP status code sent back by the server
statusText Returns the text description of the status code
responseXML Returns the response if it is an XML document
responseText Returns the response as a string
setRequestHeader(name, value) Sets a header on the request
getAllResponseHeaders() Returns all of the headers in the response as a single string
getResponseHeader(name) Returns the value of the specified response header
abort() Terminates the request

image Tip  The jqXHR object can be used to configure the Ajax request, but this is more easily done using the configuration options for the ajax method, which I explain in the section “....”

You will encounter the jqXHR object in several places when working with jQuery. The first is, as I say, as the result from the ajax method, as illustrated by Listing 15-2.

Listing 15-2.  Using the jqXHR Object

...
<script type="text/javascript">
    $(document).ready(function () {
        var jqxhr =$.ajax("mydata.json", {
            success: function (data) {
                var tmplElems = $("#flowerTmpl").template({flowers: data}).filter("*");
                tmplElems.slice(0, 3).appendTo("#row1");
                tmplElems.slice(3).appendTo("#row2");
            }
        });
  
        var timerID = setInterval(function () {
            console.log("Status: " + jqxhr.status + " " + jqxhr.statusText);
            if (jqxhr.readyState == 4) {
                console.log("Request completed: " + jqxhr.responseText);
                clearInterval(timerID);
            }
        }, 100);
  
    });
</script>
...

In this listing, I assign the result from the ajax method to a variable called jqxhr and use the setInterval method to write information about the request to the console every 100 milliseconds. Using the result of the ajax method doesn’t change the fact that the request is performed asynchronously, so caution is required when working with the jqXHR object. I use the readyState property to check the status of the request (the value of 4 indicates the request has completed) and write the response from the server to the console. This script produces the following console output (although you might see something slightly different based on your browser configuration):

Status: 200 OK
Request completed: [{"name":"Aster","product":"aster","stocklevel":"10","price":"2.99"}, {"name":"Daffodil","product":"daffodil","stocklevel":"12","price":"1.99"}, {"name":"Rose","product":"rose","stocklevel":"2","price":"4.99"}, {"name":"Peony","product":"peony","stocklevel":"0","price":"1.50"}, {"name":"Primula","product":"primula","stocklevel":"1","price":"3.12"}, {"name":"Snowdrop","product":"snowdrop","stocklevel":"15","price":"0.99"}]

image Tip  I rarely use the jqXHR object and never when it is the result of the ajax method. If I want to work with the jqXHR object (typically to get additional information about the response from a server), then I usually do so through the event handler settings that I describe in the section “Handling Ajax Callbacks.” They give me a context regarding the status of the request without making me poll for request status.

Setting the Request URL

As an alternative to passing the URL for the request as an argument to the ajax method, you can define a url property in the map object, as shown in Listing 15-3.

Listing 15-3. Using the url Property

...
<script type="text/javascript">
    $(document).ready(function () {
        $.ajax({
            url: "mydata.json",
            success: function (data) {
                var tmplElems = $("#flowerTmpl").template({flowers: data}).filter("*");
                tmplElems.slice(0, 3).appendTo("#row1");
                tmplElems.slice(3).appendTo("#row2");
            }
        });
    });
</script>
...

Making a POST Request

You set the HTTP method for requests using the type setting. The default is to make GET requests, as in the previous example. Listing 15-4 shows using the ajax method to create a POST request and submit form data to the server.

Listing 15-4.  Creating a POST Request with the ajax Method

...
<script id="totalTmpl" type="text/x-handlebars-template">
    <div id="totalDiv" style="clear: both; padding: 5px">
        <div style="text-align: center">Total Items:
            <span id=total>{{total}}</span></div>
    </div>
</script>
<script type="text/javascript">
    $(document).ready(function () {
        $.ajax({
            url: "mydata.json",
            success: function (data) {
                var tmplElems = $("#flowerTmpl").template({flowers: data}).filter("*");
                tmplElems.slice(0, 3).appendTo("#row1");
                tmplElems.slice(3).appendTo("#row2");
            }
        });
  
        $("button").click(function (e) {
            $.ajax({
                url: $("form").attr("action"),
                data: $("form").serialize(),
                type: "post",
                success: processServerResponse
            })
            e.preventDefault();
        })
  
        function processServerResponse(data) {
            var inputElems = $("div.dcell").hide();
            for (var prop in data) {
                var filtered = inputElems.has("input[name=' + prop + ']")
                    .appendTo("#row1").show();
            }
            $("#buttonDiv").remove();
            $("#totalTmpl").template(data).appendTo("body");
        }
    });
</script>
...

I have used several settings in addition to type. To specify the target for the POST request, I used the url property, which I take from the target of the form element in the document. I specify the data to send using the data property, which I set by using the serialize method (described in Chapter 34).

GOING BEYOND GET AND POST

You can use the type property to specify any HTTP method, but you may have difficulty using anything other than GET or POST because many firewalls and application servers are configured to discard other kinds of request. If you want to use other HTTP methods, then you can make a POST request, but add the X-HTTP-Method-Override header, setting it to the method you want to use, as follows:

X-HTTP-Method-Override: PUT

This convention is widely supported by web application frameworks and is a common way of creating RESTful web applications, which you can learn more about at http://en.wikipedia.org/wiki/Representational_state_transfer. See the section “Setting Timeouts and Headers” for details of how to set a header on a jQuery Ajax request.

Handling Ajax Callbacks

Several properties let you specify callback functions for key points in the life of an Ajax request. You already saw one of these callbacks when I used the success property in Listing 15-4. Table 15-3 describes the property used to set up each of these callbacks.

Table 15-3. The Ajax Event Properties

Setting Description
beforeSend Specifies a function that will be called before the Ajax request is started
complete Specifies a function that will be called when the Ajax request succeeds or fails
error Specifies a function that will be called when the Ajax request fails
success Specifies a function that will be called when the Ajax request succeeds

image Tip  The settings described in Table 15-3 are related to local callbacks, meaning that they deal with individual Ajax requests. You can also use a series of global events, which I describe in the section “Using the Global Ajax Events.”

Dealing with Successful Requests

When I demonstrated the use of the success property, I omitted a couple of arguments from the function: a status message describing the result of the request and a jqXHR object. Listing 15-5 shows the use of a function that accepts these arguments.

Listing 15-5.  Receiving All of the Arguments to a Success Function

...
<script type="text/javascript">
    $(document).ready(function () {
        $.ajax({
            url: "mydata.json",
            success: function (data, status, jqxhr) {
  
                console.log("Status: " + status);
                console.log("jqXHR Status: " + jqxhr.status + " " + jqxhr.statusText);
                console.log(jqxhr.getAllResponseHeaders());
  
                var tmplElems = $("#flowerTmpl").template({flowers: data}).filter("*");
                tmplElems.slice(0, 3).appendTo("#row1");
                tmplElems.slice(3).appendTo("#row2");
            }
        });
    });
</script>
...

The status argument is a string that describes the outcome of the request. The callback function specified by the success property is executed only for successful results and so this argument generally has the value success. The exception occurs when you use the ifModified setting, which I describe in the section “Ignoring Unmodified Data.” The callback functions for the other Ajax events follow the same pattern, and this argument is more useful in some of the other events.

The final argument is a jqXHR object. You don’t have to poll the status of the request before working with the jqXHR object since you know the function is executed only when the request has successfully completed. In Listing 15-5, I have used the jqXHR object to get the status information and the headers that the server has included in the response and write them to the console. This example produces the following result (although you will see a different set of headers depending on the web server you are using):

Status: success
jqXHR Status: 200 OK
Date: Thu, 20 Jun 2013 12:06:30 GMT
Last-Modified: Wed, 19 Jun 2013 16:29:49 GMT
Server: Microsoft-IIS/7.5
X-Powered-By: ASP.NET
ETag: "b680cf37a6dce1:0"
Content-Type: application/json
Cache-Control: no-cache
Accept-Ranges: bytes
Content-Length: 405

Dealing with Errors

The error property specifies a callback function to be called when a request fails. Listing 15-6 provides a demonstration.

Listing 15-6.  Using the error Property

...
<style type="text/css">
    .error {color: red; border: medium solid red; padding: 4px;
            margin: auto; width: 200px; text-align: center}
</style>
<script type="text/javascript">
    $(document).ready(function () {
        $.ajax({
            url: "NoSuchFile.json",
            success: function (data, status, jqxhr) {
                var tmplElems = $("#flowerTmpl").template({flowers: data}).filter("*");
                tmplElems.slice(0, 3).appendTo("#row1");
                tmplElems.slice(3).appendTo("#row2");
            },
            error: function (jqxhr, status, errorMsg) {
                $("<div>").addClass("error")
                    .text("Status: " + status + " Error: " + errorMsg)
                    .insertAfter("h1");
            }
        });
    });
</script>
...

In Listing 15-6, I have requested a file called NoSuchFile.json , which doesn’t exist on the web server. This ensures that the request will fail and the callback function I have specified with the error property will be invoked.

The arguments passed to the error callback function are a jqXHR object, a status message, and the error message from the server response. In the listing, I use the error callback to add a div element to the document showing the value of the status and errorMsg arguments, as shown in Figure 15-1.

9781430263883_Fig15-01.jpg

Figure 15-1. Displaying an error message

The status argument can be one of the values shown in Table 15-4.

Table 15-4. The Error Status Values

Setting Description
abort Indicates that the request was aborted (using the jqXHR object)
error Indicates a general error, usually reported by the server
parsererror Indicates that the data returned by the server could not be parsed
timeout Indicates that the request timed out before the server responded

The value of the errorMsg argument varies based on the status. When the status is error, then errorMsg will be set to the text portion of the response from the server. So, in this example, the response from the server was 404 Not Found, and so errorMsg is set to Not Found.

When the status is timeout, the value of errorMsg will also be timeout. You can specify the period before a request times out using the timeout setting, which I describe in the section “Setting Timeouts and Headers.”

When the status is parsererror, then errorMsg will contain details of the problem. This error occurs when data is malformed or the server returns the wrong MIME type for the data. (You can override the data type using the dataType setting.) Finally, when the request is abort, both the status and the errorMsg values will be abort.

image Tip  Although I have displayed the status and errorMsg values in the document, this is generally unhelpful to the user, since the messages require some understanding of what’s happening inside the web application and they contain no instructions about how the problem might be resolved.

Dealing with Completed Requests

The complete property specifies a function that will be called when the Ajax request completes, irrespective of whether it succeeds or fails. Listing 15-7 provides a demonstration.

Listing 15-7.  Using the Complete Property

...
<script type="text/javascript">
    $(document).ready(function () {
        $.ajax({
            url: "mydata.json",
            success: function (data, status, jqxhr) {
                var tmplElems = $("#flowerTmpl").template({flowers: data}).filter("*");
                tmplElems.slice(0, 3).appendTo("#row1");
                tmplElems.slice(3).appendTo("#row2");
            },
            error: function (jqxhr, status, errorMsg) {
                $("<div>").addClass("error")
                    .text("Status: " + status + " Error: " + errorMsg)
                    .insertAfter("h1");
            },
            complete: function (jqxhr, status) {
                console.log("Completed: " + status);
            }
        });
    });
</script>
...

The callback function specified by the complete property is called after the functions specified by the success and error properties. jQuery passes the jqXHR object and a status string to the callback function. The status string will be set to one of the values shown in Table 15-5.

Table 15-5. The Ajax Event Settings

Setting Description
abort Indicates that the request was aborted (using the jqXHR object)
error Indicates a general error, usually reported by the server
notmodified Indicates that the requested content has not been modified since it was last requested (see the section “Ignoring Unmodified Data” for more details)
parsererror Indicates that the data returned by the server could not be parsed
success Indicates that the request completed successfully
timeout Indicates that the request timed out before the server responded

You might be tempted to use the complete setting to specify a single function that can handle all outcomes of a request, but doing so means you don’t benefit from the way that jQuery processes data and errors. A better approach is to use the success and error settings and carefully organize the arguments on the common function, as shown in Listing 15-8.

Listing 15-8.  Using a Single Function to Handle All Request Outcomes

...
<script type="text/javascript">
    $(document).ready(function () {
        $.ajax({
            url: "mydata.json",
            success: function (data, status, jqxhr) {
                handleResponse(status, data, null, jqxhr);
            },
            error: function (jqxhr, status, errorMsg) {
                handleResponse(status, null, errorMsg, jqxhr);
            }
        });
  
        function handleResponse(status, data, errorMsg, jqxhr) {
            if (status == "success") {
                var tmplElems = $("#flowerTmpl").template({ flowers: data }).filter("*");
                tmplElems.slice(0, 3).appendTo("#row1");
                tmplElems.slice(3).appendTo("#row2");
            } else {
                $("<div>").addClass("error")
                    .text("Status: " + status + " Error: " + errorMsg)
                    .insertAfter("h1");
            }
        }
    });
</script>
...

Configuring Requests Before They Are Sent

The beforeSend property lets you specify a function that will be called before the request is started. This gives you an opportunity to do last-minute configuration that supplements or overrides the settings you passed to the ajax method, which can be useful if you are using the same basic settings object for multiple requests. Listing 15-9 demonstrates the use of the beforeSend property.

Listing 15-9.  Using the beforeSend Property

...
<script type="text/javascript">
    $(document).ready(function () {
        $.ajax({
            url: "NoSuchFile.json",
            success: function (data, status, jqxhr) {
                handleResponse(status, data, null, jqxhr);
            },
            error: function (jqxhr, status, errorMsg) {
                handleResponse(status, null, errorMsg, jqxhr);
            },
            beforeSend: function (jqxhr, settings) {
                settings.url = "mydata.json";
            }
        });
  
        function handleResponse(status, data, errorMsg, jqxhr) {
            if (status == "success") {
                var tmplElems = $("#flowerTmpl").template({ flowers: data }).filter("*");
                tmplElems.slice(0, 3).appendTo("#row1");
                tmplElems.slice(3).appendTo("#row2");
            } else {
                $("<div>").addClass("error")
                    .text("Status: " + status + " Error: " + errorMsg)
                    .insertAfter("h1");
            }
        }
  
    });
</script>
...

The arguments passed to the callback function are the jqXHR object and the settings object that you passed to the ajax method. In Listing 15-9, I used the url setting to specify the URL for the Ajax request, overriding the value of the url property.

Specifying Multiple Event Handler Functions

I have shown just one callback function to respond to the Ajax request, but you can set the success, error, complete, and beforeStart properties to an array of functions and each of them will be executed when the corresponding event is triggered. Listing 15-10 provides a demonstration.

Listing 15-10.  Specifying Multiple Event Handling Functions

...
<script type="text/javascript">
    $(document).ready(function () {
        $.ajax({
            url: "mydata.json",
            success: [processData, reportStatus],
        });
  
        function processData(data, status, jqxhr) {
            var tmplElems = $("#flowerTmpl").template({ flowers: data }).filter("*");
            tmplElems.slice(0, 3).appendTo("#row1");
            tmplElems.slice(3).appendTo("#row2");
        }
  
        function reportStatus(data, status, jqxhr) {
            console.log("Status: " + status + " Result code: " + jqxhr.status);
        }
    });
</script>
...

In Listing 15-10, I have set the success property to an array containing two function names, one of which uses the data to add elements to the document and the other of which prints information to the console.

Setting the Context for Events

The context property lets you specify an element that will be assigned to the this variable when an event function is enabled. This can be useful for targeting elements in the document without having to select them in the handler function. Listing 15-11 gives a demonstration.

Listing 15-11.  Using the context Property

...
<script type="text/javascript">
    $(document).ready(function () {
        $.ajax({
            url: "mydata.json",
            context: $("h1"),
            success: function (data, status, jqxhr) {
                var tmplElems = $("#flowerTmpl").template({ flowers: data }).filter("*");
                tmplElems.slice(0, 3).appendTo("#row1");
                tmplElems.slice(3).appendTo("#row2");
            },
            complete: function (jqxhr, status) {
                var color = status == "success" ? "green" : "red";
                this.css("border", "thick solid " + color);
            }
        });
    });
</script>
...

In Listing 15-11, I set the context property to the jQuery object containing the h1 elements in the document. In the complete callback function, I use the css method on the jQuery object (which I refer to via this) to set the border for the selected elements (or element, since there is only one in the document), varying the color based on the status of the request. You can see the result for successful and failed requests in Figure 15-2.

9781430263883_Fig15-02.jpg

Figure 15-2. Using the context property to indicate the outcome of an Ajax request

image Tip  You can assign any object using the context property, and so you are responsible for ensuring that you do appropriate things with it. For example, if you set the context to be an HTMLElement object, then you must be sure to pass the object to the $ function before calling any jQuery methods on it.

Using the Global Ajax Events

In addition to the per-request callback functions that I described in the previous chapter, jQuery also defines a set of global events, which you can use to monitor all Ajax queries that are made by your application. Table 15-6 shows the methods available for global events.

Table 15-6. jQuery Ajax Event Methods

Method Description
ajaxComplete(function) Registers a function to be called when an Ajax request completes (irrespective of whether it was successful)
ajaxError(function) Registers a function to be called when an Ajax requests encounters an error
ajaxSend(function) Registers a function to be called before an Ajax request commences
ajaxStart(function) Registers a function to be called when an Ajax request starts
ajaxStop(function) Registers a function to be called when all Ajax requests complete
ajaxSuccess(function) Registers a function to be called when an Ajax request succeeds

image Tip  Prior to jQuery 1.9, you could call the methods in the table on any element, but from jQuery 1.9/2.0 you can only call the methods in the table on the document element, as shown in the examples in this section.

These methods are used to register handler functions and must be applied to the document element (as I demonstrate shortly). The ajaxStart and ajaxStop methods do not pass any arguments to handler functions, but the other methods provide the following arguments:

  • An Event object describing the event
  • A jqXHR object describing the request
  • The settings object that contains the configuration for the request

The ajaxError method passes an additional argument to the handler function, which is the description of the error that has occurred.

There are two important things to remember about these methods. The first is that the functions will be triggered for events from all Ajax requests, which means you have to be careful to ensure that you are not making assumptions that are true only for a specific request.

The second thing to remember is that you need to call these methods before you start making Ajax requests to ensure that the handler functions are properly triggered. If you call the global methods after calling the ajax method, you run the risk that the Ajax request will have finished before jQuery can properly register your handler function. Listing 15-12 provides a demonstration of using the global Ajax event methods.

Listing 15-12.  Using the Global Ajax Event Methods

...
<script type="text/javascript">
    $(document).ready(function () {
  
        $("<div").append("<label>Events:<label>")
        .append("<input type='checkbox' id='globalevents' name='globalevents' checked>")
            .insertAfter("h1");
        $("<ol id='info' class='ajaxinfo'>").insertAfter("h1").append("<li>Ready</li>");
  
        function displayMessage(msg) {
            $("#info").append($("<li>").text(msg));
        }
  
        $(document)
            .ajaxStart(function () {
                displayMessage("Ajax Start")
            })
            .ajaxSend(function (event, jqxhr, settings) {
                displayMessage("Ajax Send: " + settings.url)
            })
            .ajaxSuccess(function (event, jqxhr, settings) {
                displayMessage("Ajax Success: " + settings.url)
            })
            .ajaxError(function (event, jqxhr, settings, errorMsg) {
                displayMessage("Ajax Error: " + settings.url)
            })
            .ajaxComplete(function (event, jqxhr, settings) {
                displayMessage("Ajax Complete: " + settings.url)
            })
            .ajaxStop(function () {
                displayMessage("Ajax Stop")
            });
  
        $("button").click(function (e) {
            $("#row1, #row2, #info").empty();
            $.ajax({
                url: "mydata.json",
                global: $("#globalevents:checked").length > 0,
                success: function (data, status, jqxhr) {
                    var tmplElems = $("#flowerTmpl")
                        .template({ flowers: data }).filter("*");
                    tmplElems.slice(0, 3).appendTo("#row1");
                    tmplElems.slice(3).appendTo("#row2");
                }
            });
            e.preventDefault();
        });
    });
</script>
...

In Listing 15-12, I have registered functions for all of the global Ajax events. These functions call the displayMessage function to show which event has been triggered. Because Ajax requests can complete quickly, I use an ol element to display the messages as they arrive, building up a list of events.

I have added a handler function for the button element’s click event that starts the Ajax request when the button is clicked. You can see the result in Figure 15-3, which shows the messages generated by the Ajax request once the button has been clicked.

9781430263883_Fig15-03.jpg

Figure 15-3. Displaying the global Ajax events

Controlling Global Events

You will notice that I added a check box to the document. In the call to the ajax function, I use the check box to set the value of the global setting, as shown in Listing 15-13.

Listing 15-13.  Using the global Property

...
$.ajax({
    url: "mydata.json",
    global: $("#globalevents:checked").length > 0,
    success: function (data, status, jqxhr) {
        var tmplElems = $("#flowerTmpl").template({ flowers: data }).filter("*");
        tmplElems.slice(0, 3).appendTo("#row1");
        tmplElems.slice(3).appendTo("#row2");
    }
})
...

When the global setting is false, the Ajax request doesn’t generate the global Ajax events. You can try this yourself using the example. Uncheck the box and click the button and you will see that the Ajax request is performed without any status information being shown.

Configuring the Basic Settings for an Ajax Request

There are a group of settings that allow you to perform basic configuration of the Ajax request. These are the least interesting of the settings available, and they are largely self-evident. Table 15-7 describes these settings, and I demonstrate a small number of them in the sections that follow.

Table 15-7. Basic Request Configuration Settings

Setting Description
accepts Sets the value of the Accept request header, which specifies the MIME types that the browser will accept. By default, this is determined by the dataType setting.
cache If set to false, the content from the request will not be cached by the server. By default, the script and jsonp data types are not cached, but everything else is.
contentType Sets the Content-Type header for the request.
dataType Specifies the data type that is expected from the server. When this setting is used, jQuery will ignore the information provided by the server about the response type. See Chapter 14 for details of how this works.
headers Specifies additional headers and values to add to the request; see the following discussion for a demonstration.
jsonp Specifies a string to use instead of a callback when making JSONP requests. This requires coordination with the server. See Chapter 14 for details about JSONP.
jsonpCallback Specifies the name for the callback function, replacing the randomly generated name that jQuery uses by default. See Chapter 14 for details of JSONP.
password Specifies a password to use in response to an authentication challenge.
scriptCharset When requesting JavaScript content, tells jQuery that the script is encoded with the specified character set.
timeout Specifies the timeout (in milliseconds) for the request. If the request times out, then the function specified by the error setting will be called with a status of timeout.
username Specifies a username to use in response to an authentication challenge.

Setting Timeouts and Headers

Users are often not aware of Ajax requests happening, so setting a timeout period is a good way to avoid leaving the user hanging around waiting for a process they don’t even know is occurring. Listing 15-14 shows how you can set a timeout on a request.

Listing 15-14.  Setting Timeouts

...
<script type="text/javascript">
    $(document).ready(function() {
          
        $.ajax("mydata.json", {
            timeout: 5000,
            headers: { "X-HTTP-Method-Override": "PUT" },
            success: function(data, status, jqxhr) {
                var template = $("#flowerTmpl");
                template.tmpl(data.slice(0, 3)).appendTo("#row1");
                template.tmpl(data.slice(3)).appendTo("#row2");
            },
            error: function(jqxhr, status, errorMsg) {
                console.log("Error: " + status);
            }
       });
    });
</script>
...

In Listing 15-14, I have used the timeout setting to specify a maximum duration for the request of five seconds. If the request hasn’t completed in that time, then the function specified by the error setting will be executed, with a status value of error.

image Caution  The timer starts as soon as the request is passed to the browser, and most browsers put limits on the number of concurrent requests. This means you run the risk of timing out requests before they even start. To avoid this, you must have some awareness of the limits of the browser and the volume and expected duration of any other Ajax requests that are in progress.

In Listing 15-14, I also used the headers setting to add a header to the request, as follows:

...
headers: { "X-HTTP-Method-Override": "PUT" },
...

Additional headers are specified using a map object. The header in the example is the one I mentioned in a previous section “Making a POST Request.” This header can be useful for creating a RESTful web application, as long as it is properly understood by the server.

Sending JSON Data to the Server

When you need to send data to the server, you can do so using the JSON format: it is a compact and expressive data format and easy to generate from JavaScript objects. The process for sending JSON is simple: just use the contentType property to set the Content-Type header in the request, which tells the server the kind of data being sent as demonstrated by Listing 15-15.

Listing 15-15.  Sending JSON to the Server

...
<script type="text/javascript">
    $(document).ready(function () {
  
        $.ajax("mydata.json", {
            success: function (data, status, jqxhr) {
                var tmplElems = $("#flowerTmpl").template({ flowers: data }).filter("*");
                tmplElems.slice(0, 3).appendTo("#row1");
                tmplElems.slice(3).appendTo("#row2");
            }
        });
  
        $("button").click(function (e) {
            $.ajax({
                url: $("form").attr("action"),
                contentType: "application/json",
                data: JSON.stringify($("form").serializeArray()),
                type: "post",
                success: processServerResponse
            })
            e.preventDefault();
        })
  
        function processServerResponse(data) {
            var inputElems = $("div.dcell").hide();
            for (var prop in data) {
                var filtered = inputElems.has("input[name=" + prop + "]")
                    .appendTo("#row1").show();
            }
            $("#buttonDiv, #totalDiv").remove();
            $("#totalTmpl").template(data).appendTo("body");
        }
    });
</script>
...

I have used the contentType setting to specify a value of application/json, which is the MIME type for JSON. I could have sent any object to the server, but I wanted to demonstrate how you express form data as JSON, as follows:

...
data: JSON.stringify($("form").serializeArray()),
...

I select the form element and call the serializeArray method; this creates an array of objects, each of which has a name property and a value property representing one of the input elements in the form. I then use the JSON.stringify method to convert this into a string like the following:

[{"name":"aster","value":"1"}, {"name":"daffodil","value":"1"},
 {"name":"rose","value":"1"}, {"name":"peony","value":"1"},
 {"name":"primula","value":"1"},{"name":"snowdrop","value":"1"}]

And so I have a JSON string that describes an array of objects that I can send to the server. The Node.js script that I am using for this chapter is able to parse and process this object.

Using Advanced Configuration Settings

In the sections that follow, I describe the most interesting and useful of the advanced settings that you can apply to an Ajax request. I find that I don’t use these often, but they are invaluable when required and they provide fine-grained control over how jQuery deals with Ajax.

Making the Request Synchronously

The async property specifies whether the request will be performed asynchronously. Setting this property to true (which is the default value used if the property isn't defined) means that it will performed asynchronously; a value of false means that the request will be performed synchronously.

When the request is performed synchronously, the ajax method behaves like a normal function, and the browser will wait for the request to complete before moving on to execute other statements in the script. Listing 15-16 gives an example.

Listing 15-16.  Making a Synchronous Request

...
<script type="text/javascript">
    $(document).ready(function() {
        var elems;
          
        $.ajax("flowers.html", {
            async: false,
            success: function(data, status, jqxhr) {
                elems = $(data).filter("div").addClass("dcell");
            }
        });
                          
        elems.slice(0, 3).appendTo("#row1");
        elems.slice(3).appendTo("#row2");
    });
</script>
...

This is the request that I showed in Chapter 14 to demonstrate the most common pitfall when using Ajax, updated to use the low-level API. The difference in this case is that the async setting is false, and so the browser won’t get to the statements that call the slice and appendTo methods until the request has been completed and the results are assigned to the elems variable (assuming that the request completed successfully). Making synchronous calls using the Ajax method is an odd thing to do, and I recommend you consider why your web application needs to do this.

I often use this technique as a quick test when looking a troublesome Ajax code—not dealing with asynchronous requests properly is such a common problem that I start debugging with a quick synchronous test. If the code works, I know to start looking for bad assumptions about when data will arrive from the server.

image Tip  Do not use synchronous calls because you find making asynchronous calls arduous; I appreciate that using callbacks and making sure you don’t make assumptions about the outcome of requests can be tiresome, but it really is worth the time to get your head around this approach to web programming.

Ignoring Unmodified Data

You can use the ifModified property to receive data only if the response has changed since the last time you queried it; this is determined by the Last-Modified header in the response. If you need to request the same data repeatedly in response to a user action, you often end up processing the server response and modifying the document just to present the user with whatever was already there. The default value for this setting is false, which tells jQuery to ignore the header and always return the data. Listing 15-17 provides a demonstration of using this property.

Listing 15-17.  Using the ifModified property

...
<script type="text/javascript">
    $(document).ready(function () {
  
        $("button").click(function (e) {
            $.ajax("mydata.json", {
                ifModified: true,
                success: function (data, status) {
                    if (status == "success") {
                        $("#row1, #row2").children().remove();
                        var tmplElems = $("#flowerTmpl")
                            .template({ flowers: data }).filter("*");
                        tmplElems.slice(0, 3).appendTo("#row1");
                        tmplElems.slice(3).appendTo("#row2");
                    } else if (status == "notmodified") {
                        $("img").css("border", "thick solid green");
                    }
                }
            });
            e.preventDefault();
        })
    });
</script>
...

In Listing 15-17, the value of the ifModified setting is true. The success function is always called, but if the content has not been modified since I last requested it, then the data argument will be undefined and the status argument will be notmodified.

In this example, I perform different actions based on the status argument. If the argument is success, then I use the data argument to add elements to the document. If the argument is notmodified, then I use the css method to add a border to the img elements already in the document.

I make the call to the ajax method in response to the click event from the button element. This allows me to make the same request repeatedly to demonstrate the effect of the ifModified setting, which you can see in Figure 15-4.

9781430263883_Fig15-04.jpg

Figure 15-4. Using the ifModified setting

image Caution  This can be a useful setting but should be used with care. If you are making a request as a consequence of a user action (say, a button press), there is possibility that the user is pressing the button because the previous request didn’t perform the way it was supposed to. Imagine that you request the data but the success method contains a bug that doesn’t properly update the document with the content; the user is pressing the button to try to get the document to display properly. By using the ifModified setting unwisely, you can end up ignoring the user action, forcing the user to take more serious steps to resolve the problem.

Dealing with the Response Status Code

The statusCode property allows you to respond to the different status codes that can be returned in HTTP responses. You can use this feature as an alternative or complement to the success and error properties. Listing 15-18 shows how you can use the statusCode setting on its own.

Listing 15-18.  Using the statusCode Property

...
<style type="text/css">
    .error {color: red; border: medium solid red; padding: 4px;
            margin: auto; width: 200px; text-align: center}
</style>
<script type="text/javascript">
    $(document).ready(function() {
      
        $.ajax({
            url: "mydata.json",
            statusCode: {
                200: handleSuccessfulRequest,
                404: handleFailedRequest,
                302: handleRedirect
            }
        });
          
        function handleSuccessfulRequest(data, status, jqxhr) {
            $("#row1, #row2").children().remove();
            var template = $("#flowerTmpl");
            template.tmpl(data.slice(0, 3)).appendTo("#row1");
            template.tmpl(data.slice(3)).appendTo("#row2");
        }
          
        function handleRedirect() {
            // this function will neber be called
        }
          
        function handleFailedRequest(jqxhr, status, errorMsg) {
            $("<div class=error>Code: " + jqxhr.status + " Message: "
                + errorMsg + "</div>").insertAfter("h1");
        }
    });
</script>
...

The statusCode property is assigned an object that maps between HTTP status codes and the functions you want executed when they are returned to the server. In Listing 15-18, I have defined three functions and associated them with the status codes 200, 404, and 302.

The arguments passed to the functions depend on whether the status code reflects a successful request or an error. If the code represents a success (such as 200) then the arguments are the same as for the success callback function. For failure status codes, such as the 404 code, which indicates the requested file can’t be found, the arguments are the same as for the error callback function.

Notice that I have also added a map for the 302 code. This is sent back to the browser when the server wants to redirect you to another URL. jQuery automatically follows redirections until it receives some content or encounters an error. This means that my function for the 302 code won’t ever be called.

image Tip  The 304 code, indicating that content has not been modified since it was last requested, is generated only if the ifModified setting has been used. Otherwise, jQuery sends a 200 code. See the previous section “Ignoring Unmodified Data” for information about the ifModified setting.

I find this feature useful when I am debugging interactions between the browser and the server, typically to find out why jQuery isn’t behaving quite the way I would like. When I do this, I use the statusCode setting to complement the success and error settings and print out information to the console.

image Tip  The success or error callback function is executed before those specified by the statusCode setting.

Cleaning Up the Response Data

The dataFilter property specifies a function that will be called to process the data returned by the server. This is a useful feature when the server sends you data that aren’t quite what you need, either because the formatting isn’t perfect or because it contains data that you don’t want processed. Listing 15-19 shows the use of the dataFilter property.

Listing 15-19.  Using the dataFilter Property

...
<script type="text/javascript">
    $(document).ready(function () {
  
        $.ajax({
            url: "mydata.json",
            success: function (data, status, jqxhr) {
                $("#row1, #row2").children().remove();
                var tmplElems = $("#flowerTmpl").template({ flowers: data }).filter("*");
                tmplElems.slice(0, 3).appendTo("#row1");
                tmplElems.slice(3).appendTo("#row2");
            },
            dataType: "json",
            dataFilter: function (data, dataType) {
                if (dataType == "json") {
                    var filteredData = $.parseJSON(data);
                    filteredData.shift();
                    return JSON.stringify(filteredData.reverse());
                } else {
                    return data;
                }
            }
        });
    });
</script>
...

The function is passed the data received from the server and the value of the dataType setting. If the dataType setting has not been used, then the second function argument will be undefined. The purpose of the dataFilter function is to return the filtered data, and in Listing 15-19 I focus on the json data type, as follows:

...
var filteredData = $.parseJSON(data);
filteredData.shift();
return JSON.stringify(filteredData.reverse());
...

I convert the JSON data into a JavaScript array by using the jQuery parseJSON data (this is one of the jQuery utility methods I describe in Chapter 34). I then use the shift method to remove the first item in the array and use the reverse method to reverse the order of the remaining items.

The dataFilter callback function has to return a string, so I call the JSON.stringify method, even though I know that jQuery will convert the data back into a JavaScript object before calling the success function. That aside, you can see that I am able to remove an element from the array and reverse the remaining items—and while this is not the most useful transformation, it does demonstrate the filtering effect, which you can see in Figure 15-5.

9781430263883_Fig15-05.jpg

Figure 15-5. Removing an item and reversing the order of the data using the dataFilter setting

Managing Data Conversion

I have saved one of my favorite properties until last. You will have noticed that jQuery does some handy conversions when it receives certain data types. As an example, when jQuery receives some JSON data, it presents the success function with a JavaScript object, rather than the raw JSON string.

You can control these conversions using the converters property. The value for this setting is an object that maps between data types and functions that are used to process them. Listing 15-20 shows how you can use this property to automatically parse HTML data into a jQuery object.

Listing 15-20.  Using the converters Setting

...
<script type="text/javascript">
    $(document).ready(function() {
      
        $.ajax({
            url: "flowers.html",
            success: function(data, status, jqxhr) {
                var elems = data.filter("div").addClass("dcell");
                elems.slice(0, 3).appendTo("#row1");
                elems.slice(3).appendTo("#row2");
            },
            converters: {
                "text html": function(data) {
                    return $(data);
                }
            }
        });
    });
</script>
...

I registered a function for the text html type. Notice that you use a space between the components of the MIME type (as opposed to text/html). The function is passed the data that have been received from the server and returns the converted data. In this case, I pass the HTML fragment that is obtained from the flowers.html file to the jQuery $ function and return the result. This means I can call all the usual jQuery methods on the object passed as the data argument to the success function.

image Tip  The data types don’t always match the MIME types that are returned by the server. For example, application/json is usually presented as "text json" to the converters method.

It is easy to get carried away with these converters. I try to avoid the temptation to do more in these functions than I should. For example, I am sometimes tempted to take JSON data, apply a data template, and pass the resulting HTML elements back. And although this is a nice trick, it can trip you up if someone else tries to extend your code or you need to unwind heavy processing to get at the raw data later.

Setting Up and Filtering Ajax Requests

To finish this chapter, I am going to describe a couple of additional methods that jQuery provides to make configuring requests simpler.

Defining Default Settings

The ajaxSetup method specifies settings that will be used for every Ajax request, freeing you from having to define all of the settings you are interested in for each and every request. Listing 15-21 shows this method in use.

Listing 15-21.  Using the ajaxSetup Method

...
<script type="text/javascript">
    $(document).ready(function() {
      
        $.ajaxSetup({
            timeout: 15000,
            global: false,
            error: function(jqxhr, status, errorMsg) {
                $("<div class=error/>")
                    .text("Status: " + status + " Error: " + errorMsg)
                    .insertAfter("h1");
            },
            converters: {
                "text html": function(data) {
                    return $(data);
                }
            }
        });
      
        $.ajax({
            url: "flowers.html",
            success: function(data, status, jqxhr) {
                var elems = data.filter("div").addClass("dcell");
                elems.slice(0, 3).appendTo("#row1");
                elems.slice(3).appendTo("#row2");
            },
        });
    });
</script>
...

You call the ajaxSetup method against the jQuery $ function, just as you do for the ajax method. The argument to the ajaxSetup is an object that contains the settings you want to use as the defaults for all your Ajax requests. In Listing 15-21, I define defaults for the timeout, global, error, and converters settings. Once I have called the ajaxSetup method, I only have to define values for those settings for which I haven’t provided a default value or whose value I want to change. This can reduce code duplication when making a lot of Ajax requests that have similar configurations.

image Tip  The settings specified by the ajaxSetup method also affect requests made by the convenience and shorthand methods I showed you in Chapter 14. This can be a nice way of combining the detailed control that comes with the low-level API with the simplicity of the convenience methods.

Filtering Requests

You can use the ajaxPrefilter method if you want to dynamically tailor the settings for individual requests, as shown in Listing 15-22.

Listing 15-22.  Using the ajaxPrefilter Method

...
<script type="text/javascript">
    $(document).ready(function () {
  
        $.ajaxSetup({
            timeout: 15000,
            global: false,
            error: function (jqxhr, status, errorMsg) {
                $("<div class=error/>")
                    .text("Status: " + status + " Error: " + errorMsg)
                    .insertAfter("h1");
            },
            converters: {
                "text html": function (data) {
                    return $(data);
                }
            }
        });
  
        $.ajaxPrefilter("json html", function (settings, originalSettings, jqxhr) {
            if (originalSettings.dataType == "html") {
                settings.timeout = 2000;
            } else {
                jqxhr.abort();
            }
        });
  
        $.ajax({
            url: "flowers.html",
            success: function (data, status, jqxhr) {
                var elems = data.filter("div").addClass("dcell");
                elems.slice(0, 3).appendTo("#row1");
                elems.slice(3).appendTo("#row2");
            },
        });
    });
</script>
...

The arguments to the ajaxPrefilter method are a set of data types and a callback function that will be executed when a request for those data types is made. If you omit the data type and just specify the function, it will be invoked for all requests.

The arguments passed to the callback function are the settings for the request (which includes any defaults you have set using the ajaxSetup method); the original settings passed to the Ajax method (which excludes any default values) and the jqXHR object for the request. You make changes to the object passed as the first argument, as shown in the example.

In Listing 15-22, I filter JSON and HTML requests so that if a dataType setting has been specified in the settings passed to the Ajax method, I set the timeout to be two seconds. For a request that doesn’t have that setting, I call the abort method on the jqXHR object to prevent the request from being sent.

Summary

In this chapter, I showed you the low-level jQuery Ajax interface, which, as I hope you agree, isn’t that much harder to work with than the convenience and shorthand methods I showed you in Chapter 14. For a modicum of additional effort, you can control many aspects of the way that the Ajax request is processed, giving you endless ways in which you can tweak the process to your needs and preferences. In Chapter 16, I refactor the example to incorporate the features and techniques that I described in this part of the book.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.250.11