CHAPTER 14

image

Using Ajax: Part I

Ajax stands for Asynchronous JavaScript and XML but is generally a word in its own right these days. Ajax allows you to make requests to the server asynchronously, meaning, in short, that your request happens in the background and doesn’t prevent the user from interacting with the content in your HTML document. The most common use for Ajax is to submit data from a form element. The advantage of doing this is that the browser doesn’t have to load a new document to display the server’s response and you can use the standard jQuery functions to display the data within the document seamlessly.

The Ajax support that I use in this chapter is built into the core jQuery library, although I do briefly describe a useful plug-in at the end of the chapter. jQuery doesn’t reinvent Ajax but, rather, makes the existing browser Ajax API (application programming interface) easier to use. In this chapter, I describe the shorthand and convenience Ajax methods. These are simpler methods that make using Ajax relatively quick and easy. In Chapter 15, I describe the low-level jQuery Ajax API on which these methods are based. However, as you’ll see, the low-level API isn’t that low level, and the main reason for its use is when the shorthand and convenience methods don’t do quite what you want. Table 14-1 provides the summary for this chapter.

Table 14-1. Chapter Summary

Problem Solution Listing
Perform an asynchronous HTTP GET request Use the get method 1–3
Process the data obtained from an Ajax GET request Pass a function to the get method 4
Perform an Ajax request in response to a user action Call the get method within an event handler 5
Request JSON data from the server Use the get method and receive an object in the argument function 6, 7
Send data to the server as part of a GET request Pass a JavaScript object as an argument to the get method 8
Perform an asynchronous HTTP POST request Use the post method 9, 10
Send non-form data in a POST request Pass any JavaScript object as an argument to the post method 11
Override the data type specified by the server in the response to an Ajax request Pass the expected type as an argument to the get or post methods 12–13
Avoid the most common Ajax pitfall Don’t treat Ajax requests as though they were synchronous 14
Use the convenience methods to make GET requests for specific data types Use the load, getScript, or getJSON method 15–22
Easily enable Ajax for form elements Use the Ajax Forms plug-in 23

Using the Ajax Shorthand Methods

Although Ajax is usually associated with posting form data, it can be used a lot more widely. I am going to start introducing Ajax by performing some simple tasks, starting with ways that you can obtain data from the server without using forms at all.

jQuery defines a set of Ajax shorthand methods, which are convenient wrappers around the core Ajax functions and which allow you to easily perform common Ajax tasks. In the sections that follow, I introduce you to the shorthand methods for retrieving data from the server using HTTP GET requests. Table 14-2 summarizes these methods.

Table 14-2. The jQuery Ajax Shorthand Methods

Name Description
$.get() Performs an Ajax request using the HTTP GET method
$.post() Performs an Ajax request using the HTTP POST method

(BRIEFLY) UNDERSTANDING ASYNCHRONOUS TASKS

For those of you who are new to Ajax, here’s a simple explanation of asynchronous requests. This is important because these tasks are so central to Ajax that the first letter in the acronym stands for asynchronous. Most of the time, you are used to writing synchronous code. You define a block of statements that perform some task, and then you wait while the browser executes them. When the last statement has been executed, you know the task has been performed. During the execution, the browser doesn’t let the user interact with the content in any way.

When you perform an asynchronous task, you are telling the browser that you want something done in the background. The phrase “in the background” is something of a catchall, but in essence you are saying, “Do this thing without preventing the user from interacting with the document and tell me when you have done it.” In the case of Ajax, you are telling the browser to communicate with the server and notify you when the request has been completed. This notification is handled through callback functions. You give jQuery one or more functions that will be called when the task is complete. There will be a function to deal with a successful request, and there can be other functions for other outcomes, such as errors.

The advantage of asynchronous requests is that they allow you to create a rich HTML document that can be seamlessly updated using responses from the server without interrupting the user’s interaction and without having to make the user wait while the browser loads a new document.

The disadvantage is that you have to think through your code carefully. You can’t predict when an asynchronous request will be completed, and you can’t make assumptions about the outcome. Further, the use of callback functions tends to create more complex code that can punish the unwary programmer who makes assumptions about the outcome or timeliness of a request.

Performing an Ajax GET Request

To begin, I am going to use Ajax to perform an HTTP GET request to load a fragment of HTML, which I will then add to the HTML document displayed by the browser. Listing 14-1 shows the example document that I will be working with.

Listing 14-1.  The Example Document

<!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 type="text/javascript">
        $(document).ready(function() {
            // ... code will go here...
        });
    </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>

This is similar to the examples I have used in earlier chapters but there are no elements to describe the products and no data item or templates to generate them. Instead, I have created a separate file called flowers.html, which I have placed next to the example document (which is called example.html and can be found in the Source Code/Download area of the Apress web site [www.apress.com]). Listing 14-2 shows the content of flowers.html.

Listing 14-2.  The Contents of the flowers.html File

<div>
    <img src="aster.png"/><label for="aster">Aster:</label>
    <input name="aster" value="0" required />
</div>
<div>
    <img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
    <input name="daffodil" value="0" required />
</div>
<div>
    <img src="rose.png"/><label for="rose">Rose:</label>
    <input name="rose" value="0" required />
</div>
<div>
    <img src="peony.png"/><label for="peony">Peony:</label>
    <input name="peony" value="0" required />
</div>
<div>
    <img src="primula.png"/><label for="primula">Primula:</label>
    <input name="primula" value="0" required />
</div>
<div>
    <img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
    <input name="snowdrop" value="0" required />
</div>

These are the same elements I used in earlier chapters, except that they are not assigned to rows and I have removed the class attribute from the div elements. I have made these changes so that I can show you how to work with the elements once you load them. Notice that this isn’t a complete HTML document, just a fragment—there are no html, head, or body elements, for example. At the moment, the flowers.html document is completely separate from the main example, and you can see this in Figure 14-1.

9781430263883_Fig14-01.jpg

Figure 14-1. The initial example document

I am going to use the jQuery support for Ajax to bring the HTML fragment into the main HTML document. This might seem like an odd thing to do, but I am simulating a common situation where different pieces of content are produced by different systems and need to be stitched together to create a complex document or web application. I am using only one server in this example for simplicity, but it is easy to imagine that the information about the products is coming from elsewhere. In fact, in later examples, I introduce Node.js in order to show you how to deal with multiple servers. That is all to come. For the moment, let’s look at the basic jQuery Ajax support and use it to deal with the flowers.html file. Listing 14-3 shows how I do this.

Listing 14-3.  Using jQuery Ajax Support with an HTML Fragment

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

I used the jQuery get method and provided two arguments. The first argument is the URL that I want to load. In this case, I have specified flowers.html, which will be interpreted as being a URL that is relative to the URL from which the main document was loaded.

The second argument is a function that will be invoked if the request is successful. As I mentioned in the sidebar, Ajax relies on callback functions to provide notification because the requests are performed asynchronously. jQuery passes the data from the server response as the argument to the function.

When the document that contains this script is loaded into the browser, the script element is executed and my jQuery code loads the flowers.html from the server. Once the flowers.html document is loaded, the HTML fragment it contains is parsed into HTML elements that are then added to the document. Figure 14-2 shows the result.

9781430263883_Fig14-02.jpg

Figure 14-2. The effect of using Ajax

OK, so I admit that I ended up with the same result as when the elements or data were inline, but the path I took to get there is worth exploring and in the sections that follow I dig into the detail.

image Tip  Although I have used the get method to load HTML, it can be used to obtain any kind of data from the server.

Processing the Response Data

The argument passed to the success function is the data that the server has sent back in answer to my request. In this example, I get back the content of the flowers.html file, which is an HTML fragment. To make this into something I can use with jQuery, I passed the data into the jQuery $ function so that it is parsed into a hierarchy of HTMLElement objects, as shown in Listing 14-4.

Listing 14-4.  Processing the Data Obtained from the Server

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

As I mentioned previously, I left out the class attributes from the div elements. You can see that I add them back in using the jQuery addClass method. Once the data has been passed to the $ function, I can use the jQuery object that is returned as I would any other. I add the elements to the document using the slice and appendTo methods, as I have in previous chapters.

image Tip  Notice that I have used the filter method to select only the div elements generated from the data. When parsing the data, jQuery assumes that the carriage-return characters that I added between the div elements in the flowers.html file for structure are text content and creates text elements for them. To avoid this, you can either ensure that there are no carriage returns in the documents you request or use the filter method to remove then. This is similar to the issue I had with data templates in Chapter 13.

Making the Effect Easier to See

The statements that create the Ajax request are executed in response to the ready event (which I described in Chapter 5) and this makes it hard to visualize how using Ajax is any different from using inline data because the content of the flowers.html file is loaded and displayed automatically. To make the difference more obvious, I have added a button to the document and handle the click event it generates so that the Ajax request will be performed only when it is clicked. You can see the changes in Listing 14-5.

Listing 14-5.  Making an Ajax Request in Response to a Button Press

...
<script type="text/javascript">
    $(document).ready(function () {
        $("<button>Ajax</button>").appendTo("#buttonDiv").click(function (e) {
            $.get("flowers.html",
                function (data) {
                    var elems = $(data).filter("div").addClass("dcell");
                    elems.slice(0, 3).appendTo("#row1");
                    elems.slice(3).appendTo("#row2");
                });
            e.preventDefault();
        });
    });
</script>
...

Now the flowers.html document isn’t loaded until the button is clicked, and each time it is clicked, additional elements are added to the document, as shown in Figure 14-3.

9781430263883_Fig14-03.jpg

Figure 14-3. Using Ajax in response to a button press

image Tip  Notice that I have called the preventDefault method on the Event object that is passed to my event handler function. Since the button element is contained with a form element, the default action is to submit the form to the server.

Getting Other Kinds of Data

You are not limited to using the get method just for HTML—you can obtain any kind of data from the server. Of particular interest is JavaScript Object Notation (JSON) data, because of the way that jQuery helpfully processes the response from the server. Back when Ajax started to be widely adopted, XML was seen as the data format of choice, so much so that the X in Ajax stands for XML. I am not going to go into the details of XML, but XML tends to be verbose, hard to read, and relatively time- and resource-consuming to generate and process.

In recent years, XML has been largely replaced by the JSON, which is a simpler data format and is easy to work with in JavaScript code (as the name suggests). For this example, I have created a file called mydata.json and saved it alongside the example.html file on the web server. Listing 14-6 shows the contents of mydata.json .

Listing 14-6.  The Contents of the mydata.json File

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

The file contains the data for the flower products and as you can see, JSON data is almost identical to the way you represent data inline in JavaScript code. To load and process this data using Ajax, I can use the get method again, as shown in Listing 14-7.

Listing 14-7.  Using the get Method to Obtain JSON Data

...
<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="{{stock}}"
            value="0" required />
    </div>
    {{/flowers}}
</script>
<script type="text/javascript">
    $(document).ready(function () {
        $("<button>Ajax</button>").appendTo("#buttonDiv").click(function (e) {
            $.get("mydata.json", function (data) {
                var tmplData = $("#flowerTmpl").template({flowers: data}).filter("*");
                tmplData.slice(0, 3).appendTo("#row1");
                tmplData.slice(3).appendTo("#row2");
            });
            e.preventDefault();
        });
    });
</script>
...

In this example, I request the JSON data file in response to the button click. The data retrieved from the server is passed to a function, just as with the HTML fragment. I have used the Handlebars template plug-in (described in Chapter 12) to process the data and generate HTML elements from it and then the slice and appendTo method to insert the elements into the document. Notice that I didn’t have to do anything to convert the JSON string to a JavaScript object: jQuery does this for me automatically.

image Tip  Some web servers (and this includes some versions of Microsoft IIS, which I have used for this book) will not return content to browsers if they don’t recognize the file extension or data format. To make this example work with IIS, I have to add a new mapping between the file extension (.json) and the MIME type for JSON data (application/json). Until I did this, IIS would return 404—Not Found errors when mydata.json was requested.

Providing Data to GET Requests

It is possible to send data to the server as part of a GET request, which is the kind of request made by the get method (as well as the load, getScript, and getJSON methods that I describe later in this chapter). To send data as part of the GET request, you pass a data object to the get method, as demonstrated by Listing 14-8.

Listing 14-8.  Sending Data as Part of a GET Request

...
<script type="text/javascript">
    $(document).ready(function () {
  
        var requestData = {
            country: "US",
            state: "New York"
        };
  
        $("<button>Ajax</button>").appendTo("#buttonDiv").click(function (e) {
            $.get("mydata.json",requestData, function (data) {
                var tmplData = $("#flowerTmpl").template({flowers: data}).filter("*");
                tmplData.slice(0, 3).appendTo("#row1");
                tmplData.slice(3).appendTo("#row2");
            });
            e.preventDefault();
        });
    });
</script>
...

The data you provide is appended to the specified URL as a query string. For this example, this means you request the following:

http://www.jacquisflowershop.com/jquery/flowers.html?country=US&state=New+York

The server can use the data you provide to tailor the content that is returned. You might have different flower sections for different states, for example. You won’t be able to see the URL used to make the Ajax request in the browser, but you can use the developer’s tools (commonly known as the F12 tools because they are accessed via the F12 key) to see what requests are being made. For Google Chrome, press F12, click the Network tab in the window that appears, and click the XHR filter (XHR refers to the XmlHttpRequest object, which is the Document Object Model (DOM) object used by jQuery to make Ajax requests). Figure 14-4 illustrates how Chrome displays details of the Ajax request in the example.

9781430263883_Fig14-04.jpg

Figure 14-4. Inspecting an Ajax request using the Google Chrome F12 tools

GET AND POST: PICK THE RIGHT ONE

You might be tempted to send form data using a GET request. Be careful. The rule of thumb is that GET requests should be used for read-only information retrieval, while POST requests should be used for any operation that changes the application state.

In standards-compliance terms, GET requests are for safe interactions (having no side effects besides information retrieval), and POST requests are for unsafe interactions (making a decision or changing something). These conventions are set by the World Wide Web Consortium (W3C), at www.w3.org/Provider/Style/URI.

So, you can use GET requests to send form data to the server, but not for operations that change state. Many web developers learned this the hard way in 2005 when Google Web Accelerator was released to the public. This application pre-fetched all the content linked from each page, which is legal within HTTP because GET requests should be safe. Unfortunately, many web developers had ignored the HTTP conventions and placed simple links to “delete item” or “add to shopping cart” in their applications. Chaos ensued.

One company believed its content management system was the target of repeated hostile attacks, because all its content kept getting deleted. The company later discovered that a search-engine crawler had hit upon the URL of an administrative page and was crawling all the delete links.

Performing an Ajax POST Request

Now that you have seen how to get data from the server, I can turn my attention to how you send it—that is, how to post form data to the server. Once again, there is a shorthand method: post, which makes posting a form simple. But before I demonstrate the use of the post method, I need to extend the code for the formserver.js file so that Node.js is able to receive and process the POST requests I use in the examples.

Preparing Node.js to Receive Form Data

I need a server script that will receive data sent from the browser using the HTTP POST method, perform some simple operation on the data that has been sent, and generate a response. Listing 14-9 shows the updated version of the formserver.js file that I first used in Chapter 13.

Listing 14-9.  The Modified formserver.js File

var http = require("http");
var querystring = require("querystring");
var url = require("url");
  
var port = 80;
  
http.createServer(function (req, res) {
    console.log("[200 OK] " + req.method + " to " + req.url);
  
    if (req.method == "OPTIONS") {
        res.writeHead(200, "OK", {
            "Access-Control-Allow-Headers": "Content-Type",
            "Access-Control-Allow-Methods": "*",
            "Access-Control-Allow-Origin": "http://www.jacquisflowershop.com"
        });
        res.end();
  
    } else if (req.method == "POST") {
        var dataObj = new Object();
        var contentType = req.headers["content-type"];
        var fullBody = "";
  
        if (contentType) {
            if (contentType.indexOf("application/x-www-form-urlencoded") > -1) {
                req.on("data", function (chunk) { fullBody += chunk.toString(); });
                req.on("end", function () {
                    var dBody = querystring.parse(fullBody);
                    writeResponse(req, res, dBody,
                        url.parse(req.url, true).query["callback"])
                });
            } else {
                req.on("data", function (chunk) { fullBody += chunk.toString(); });
                req.on("end", function () {
                    dataObj = JSON.parse(fullBody);
                    var dprops = new Object();
                    for (var i = 0; i < dataObj.length; i++) {
                        dprops[dataObj[i].name] = dataObj[i].value;
                    }
                    writeResponse(req, res, dprops);
                });
            }
        }
    } else if (req.method == "GET") {
        var data = url.parse(req.url, true).query;
        writeResponse(req, res, data, data["callback"])
    }
  
    function writeResponse(req, res, data, jsonp) {
        var total = 0;
        for (item in data) {
            if (item != "_" && data[item] > 0) {
                total += Number(data[item]);
            } else {
                delete data[item];
            }
        }
        data.total = total;
        jsonData = JSON.stringify(data);
        if (jsonp) {
            jsonData = jsonp + "(" + jsonData + ")";
        }
  
        res.writeHead(200, "OK", {
            "Content-Type": "application/json",
            "Access-Control-Allow-Origin": "*"
        });
        res.write(jsonData);
        res.end();
    }
  
}).listen(port);
console.log("Ready on port " + port);

image Tip  The easiest way to get this script is to download the source code that accompanies this book and that is freely available on the Apress web site at www.apress.com. I included details of obtaining Node.js in Chapter 1.

As before, I run the script by entering the following at the command prompt:

node.exe formserver.js

The revised Node.js script processes the data sent by the browser and creates a JSON response. I could have returned HTML from this script, but JSON is more compact and often simpler to work with. The JSON object I return is a simple object that contains the total number of products that the user has selected and the number of each of them for which a value was specified. So, for example, if I selected one aster, two daffodils, and three roses, the JSON response sent back by the Node.js script would be as follows:

{"aster":"1","daffodil":"2","rose":"2","total":5}

The previous JSON string I showed you represented an array of objects, but this server script returns just a single object whose properties correspond to the selected flowers. The total property contains the sum of the individual selections. I appreciate that this is hardly the most valuable activity a server can perform, but I want to focus on using Ajax rather than server-side development.

Understanding Cross-Origin Ajax Requests

If you look at the new formserver.js script, you will see that when I write the response to the browser, I set an HTTP header, as follows:

Access-Control-Allow-Origin: http://www.jacquisflowershop.com

By default, browsers limit scripts to making make Ajax requests within the same origin as the document that contains them. An origin is the combination of the protocol, hostname, and port components of a URL. If two URLs have the same protocol, hostname, and port, then they are within the same origin. If any of the three components is different, then they are in different origins.

image Tip  This policy is intended to reduce the risks of a cross-site scripting (CSS) attack, where the browser (or user) is tricked into executing a malicious script. CSS attacks are outside the scope of this book, but there is a useful Wikipedia article at http://en.wikipedia.org/wiki/Cross-site_scripting that provides a good introduction to the topic.

Table 14-3 shows how changes in URLs affect the origin when compared to the URL of the main example document, which is www.jacquisflowershop.com/jquery/example.html.

Table 14-3. Comparing URLs

URL Origin Comparison
http://www.jacquisflowershop.com/apps/mydoc.html Same origin
https://www.jacquisflowershop.com/apps/mydoc.html Different origin; protocol differs
http://www.jacquisflowershop.com:81/apps/mydoc.html Different origin; port differs
http://node.jacquisflowershop.com/order Different origin; host differs

In my configuration, I have two servers. www.jacquisflowershop.com handles the static content, and node.jacquisflowershop.com runs Node.js. As you can see from the table, a document from the first server has a different origin from the second. When you want to make a request from one origin to another, it is known as a cross-origin request.

The problem with this policy is that it is a blanket ban; there are no cross-origin requests. This has led to the use of some ugly tricks to fool the browser into making requests that contravene the policy. Fortunately, there is now a legitimate means of making cross-origin requests, defined in the Cross-Origin Resource Sharing (CORS) specification. I am only going to describe CORS briefly. For complete details, see the full CORS standard at www.w3.org/TR/cors.

image Tip  The CORS specification is reasonably recent. It is supported by the current generation of browsers, but older browsers will simply ignore cross-origin requests. A more established approach is to use JSONP, which I describe in the section “Working with JSONP.”

The way that CORS works is that the browser contacts the second server (the Node.js server in this case) and includes an Origin header in the request. The value of this header is the origin of the document that has led to the request being made.

If the server recognizes the origin and wants to allow the browser to make a cross-origin request, then it adds the Access-Control-Allow-Origin header, setting the value to match the Origin header from the request. If the response doesn’t contain this header, then the browser discards the response.

image Tip  Supporting CORS means that the browser has to apply the cross-origin security policy after it has contacted the server and has obtained the response header, meaning that the request is made even if the response is discarded because the required header is missing or specified a different domain. This is a different approach from browsers that don’t implement CORS and that simply block the request, never contacting the server.

In the formserver.js script, I set the Access-Control-Allow-Origin header to my trusted origin http://www.jacquisflowershop.com, but you could easily use the value of the Origin header in the request to follow a more sophisticated decision process. You can also set the Access-Control-Allow-Origin header to an asterisk (*), which means that cross-origin requests from any origin will be permitted. This is fine for the purposes of testing, but you should think carefully about the security implications before using this setting in a production application.

Using the post Method to Submit Form Data

So, now that I have prepared the server and understood CORS, I am in a position to use the post method to send form data to the server, as shown by Listing 14-10.

Listing 14-10.  Sending Data with the post 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="{{stock}}"
                value="0" required />
        </div>
        {{/flowers}}
    </script>
    <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 () {
  
            $.get("flowers.html", function (data) {
                var elems = $(data).filter("div").addClass("dcell");
                elems.slice(0, 3).appendTo("#row1");
                elems.slice(3).appendTo("#row2");
            });
  
            $("button").click(function (e) {
                var formData = $("form").serialize();
                $.post("http://node.jacquisflowershop.com/order",
                    formData, 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>
</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>

This example looks more complicated than it really is. I start by using the getJSON method to obtain the mydata.json file that contains details of the flower products and then use a data template to generate elements and add them to the document. This gives me the starting point that you have come to know and love, as shown in Figure 14-5. You can see that I have entered some values into the input elements: 12 asters, 20 daffodils, 4 primula, and 4 snowdrops.

9781430263883_Fig14-05.jpg

Figure 14-5. The starting point for sending data to the server

I use the click method to register a function that will be called when the button element is clicked, as follows:

...
$("button").click(function (e) {
    var formData = $("form").serialize();
    $.post("http://node.jacquisflowershop.com/order", formData, processServerResponse);
    e.preventDefault();
});
...

The first thing that I do is to call the serialize method on the form element. This is a helpful method that works its way through all of the form elements and creates a URL-encoded string that you can send to the server.

image Tip  Notice that I call the preventDefault method on the Event object that is passed to my handler function for the click event. I need to do this to stop the browser posting the form in the regular way—that is, by sending the data and loading the response as a separate HTML document.

For the values I entered into the input elements, the serialize method generates a string as follows:

aster=12&daffodil=20&rose=0&peony=0&primula=4&snowdrop=0

I use the serialize method because the post method sends data in the URL-encoded format (although this can be changed by using the ajaxSetup global event handler method, which I describe in Chapter 15). Once I have the data from the input elements, I call the post method to initiate the Ajax request.

The arguments to the post method are the URL that I want to send the data to (which does not need to be the same as the URL specified by the action attribute of the form element), the data I want to send, and a function to call if the request is successful. In this example, I take the response from the server and pass it to the processServerResponse function, which is defined as follows:

...
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");
}
...

I hide all of the cell-level div elements in the CSS layout (which are members of the dcell class) and then display those that correspond to the properties in the JSON object from the server. I also use a new data template to generate a display for the total number of selected items. These are both activities you could have performed on the client, but the point here is that you obtained the data through an Ajax POST request. You can see the result in Figure 14-6.

9781430263883_Fig14-06.jpg

Figure 14-6. The effect of processing the data returned from the Ajax POST request

You can see how easy it is to submit form data to the server (and, of course, how easy it is to process the response, especially if it is JSON).

image Tip  If you don’t get the response shown in the figure, then the likely cause is that your CORS header isn’t being set to the correct domain in the Node.js script. As a quick test, set it to * and see what happens.

Sending Other Data Using the post Method

Although the post method is usually used to submit form data, it can actually send any data to the server. I just create an object that contains your data, call the serialize method to format the data properly, and then pass it to the post method.

This can be a useful technique if you are collecting data from the user without using a form or if you want to be selective about the form elements that you include in the POST request. Listing 14-11 shows how you can use the post method in this way.

Listing 14-11.  Using the post Method to Send Nonform Data to the Server

...
<script type="text/javascript">
    $(document).ready(function () {
  
        $("button").click(function (e) {
            var requestData = {
                apples: 2,
                oranges: 10
            };
  
            $.post("http://node.jacquisflowershop.com/order", requestData,
                function (responseData) {
                    console.log(JSON.stringify(responseData));
                })
            e.preventDefault();
        })
    });
</script>
...

In this script, I create an object and define properties explicitly. I pass this object to the post method and use the console.log method to write out the response from the server. (The formserver.js script doesn’t really care what kind of data it gets from the browser; it will just try to add up the values and generate a total.) The script produces the following console output:

{"apples":"2","oranges":"10","total":12}

image Tip  The JSON response from the server is automatically transformed into a JavaScript object by jQuery. I used the JSON.stringify method (which is supported by most browsers) to turn it back into a string so that I could display it on the console.

Specifying the Expected Data Type

When you use the get and post methods, jQuery has to figure out what kind of data the server is sending back in response to your request. It can be anything from HTML to a JavaScript file. To do this, jQuery relies on the information that the server provides in the response, particularly the Content-Type header. For the most part, this works well, but on occasion jQuery needs a little help. This is usually because the server is specifying the wrong MIME type for the data in the response.

You can override the information that the server provides and can tell jQuery what data you are expecting by passing an additional argument to the get or post methods. This argument can be one of the following values:

  • xml
  • json
  • jsonp
  • script
  • html
  • text

Listing 14-12 shows how you can specify the expected data type for the get method.

Listing 14-12.  Specifying the Expected Data Type

...
<script type="text/javascript">
    $(document).ready(function () {
        $.get("mydata.json", function (responseData) {
            console.log(JSON.stringify(responseData));
        },"json");
    });
</script>
...

You specify the data type as the last argument to the shorthand methods. In this example, I have told jQuery that I am expecting JSON data. It doesn’t matter what the server says the content type is: jQuery will treat the response as JSON. This example produces the following console output:

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

This is the same content that I put into the mydata.json file, which is, of course, what you would expect. The problem with specifying the data type is that you have to be right. If the data are actually of a different type, then you can have some problems, as demonstrated in Listing 14-13.

Listing 14-13.  Specifying the Wrong Kind of Data

...
<script type="text/javascript">
    $(document).ready(function () {
  
        $.get("flowers.html", function (responseData) {
            console.log(JSON.stringify(responseData));
        },"json");
    });
</script>
...

In this example, I have requested a file that contains HTML but told jQuery that it should treat it as JSON. The problem here is that when dealing with JSON, jQuery automatically creates a JavaScript object from the data, which it can’t do with HTML.

image Tip  I’ll show you how to detect Ajax errors in Chapter 15.

Avoiding the Most Common Ajax Pitfall

Before going any further, I want to show you the most common problem that web programmers make with Ajax, which is to treat the asynchronous request as though it were synchronous. Listing 14-14 gives an example of the problem.

Listing 14-14.  A Common Ajax Mistake

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

In this example, I have defined a variable called elems, which is then used by the Ajax callback function to assign the result of the server request. I use the slice and appendTo methods to add the elements that I have obtained from the server to the document. If you run this example, you will see that no elements are added to the document, and depending on your browser, you will see an error message displayed on the console. Following is the message shown by Google Chrome:

Uncaught TypeError: Cannot call method 'slice' of undefined

The issue here is that the statements in the script element are not executed in the order in which they are written. The code in the example assumes the following sequence will occur:

  1. Define the elems variable.
  2. Get the data from the server and assign it to the elems variable.
  3. Slice the elements in the elems variable and add them to the document.

What really happens is this.

  1. Define the elems variable.
  2. Start the asynchronous request to the server.
  3. Slice the elements in the elems variable and add them to the document.

And, at some point in the future, this happens.

  1. Receive the request from the server.
  2. Process the data and assign it to the elems variable.

In short, I get the error message because I called the slice method on a variable that doesn’t contain any elements. The worst thing about this mistake is that sometimes the code actually works. This is because the Ajax response can complete so quickly that the variable contains the data before I come to process it (this is typically the case when the data is cached by the browser or you perform some complex operations between starting the Ajax request and trying to operate on the data). You now know what to look for whether you see this kind of behavior from your code.

Using the Type-Specific Convenience Methods

jQuery provides three convenience methods that make dealing with particular types of data a little easier. Table 14-4 summarizes these methods which are demonstrated in the sections that follow.

Table 14-4. The jQuery Ajax Type-Specific Convenience Methods

Name Description
load() Loads HTML elements and inserts them into the elements in the jQuery object on which the method has been called
$.getScript() Gets and executes JavaScript code
$.getJSON() Gets JSON data

Getting an HTML Fragment

The load method will only obtain HTML data, which allows you to request an HTML fragment, process the response to create a set of elements, and insert those elements in the document in a single step. Listing 14-15 shows how you can use the load method.

Listing 14-15.  Using the Load Shorthand Method

...
<script type="text/javascript">
    $(document).ready(function () {
        $("#row1").load("flowers.html");
    });
</script>
...

You call the load method on the element in the document that you want to insert the new elements into and pass the URL as a method argument. If the request is successful and the response from the server contains valid HTML, then the elements will be inserted at the specified location, as shown in Figure 14-7.

9781430263883_Fig14-07.jpg

Figure 14-7. Adding elements to the document using the load method

The elements from the flower.html file have all been added to the document, but because they lack the class attribute they are not properly added to the CSS table layout that the main document uses. For this reason, the load method is most useful when all of the elements are to be inserted in a single location and you don’t need to modify them before they are added.

Manipulating the Elements Added by the load Method

The load method returns a jQuery object that contains the elements into which the loaded HTML content will be inserted. The key phrase is will be because the load method uses an asynchronous request to get the HTML from the server. This means that you have to be careful if you want to manipulate the elements that the load method adds to the DOM because normal jQuery techniques won’t work. Listing 14-16 shows the most common problem I see when projects use the load method.

Listing 14-16.  The Most Common Problem Code for the load Method

...
<script type="text/javascript">
    $(document).ready(function () {
        $("#row1").load("flowers.html").children().addClass("dcell");
    });
</script>
...

The objective of the code is obvious: to load the content of the flowers.html file into the row1 element, select the newly added elements, and add them to the dcell class (which will have the effect of laying them out horizontally as part of my CSS table layout).

But if you run this example, you will see that there is no change from the result shown in Figure 14-7. This is because the load method goes off and requests the flowers.html file asynchronously, leaving jQuery free to continue executing method calls. And so the child elements of the row1 element are selected and modified before the Ajax request has completed and the new elements are added to the document.

To address this, the load method has an optional argument that allows a callback function to be specified. The callback function won’t be invoked until the Ajax elements have been added to the document and this ensures that I can sequence my manipulations correctly, as shown in Listing 14-17.

Listing 14-17.  Using the Callback Argument of the load Method

...
<script type="text/javascript">
    $(document).ready(function () {
        var targetElems = $("#row1");
        targetElems.load("flowers.html", function () {
            targetElems.children().addClass("dcell");
        });
    });
</script>
...

The effect is that the calls to the children and addClass methods are not performed until after the content from the flowers.html file is added to the document, producing the effect shown in Figure 14-8.

9781430263883_Fig14-08.jpg

Figure 14-8. Using a callback function to manipulate the elements added by the load method

Getting and Executing Scripts

The getScript method loads a JavaScript file and executes the statements it contains. To demonstrate this method, I have created a file called myscript.js and saved it alongside example.html on my web server. Listing 14-18 shows the contents of this file.

Listing 14-18.  The Contents of the myscript.js File

var flowers = [
    ["Aster", "Daffodil", "Rose"],
    ["Peony", "Primula", "Snowdrop"],
    ["Carnation", "Lily", "Orchid"]
]
  
$("<div id=row3 class=drow/>").appendTo("div.dtable");
  
var fTemplate = $("<div class=dcell><img/><label/><input/></div>");
  
for (var row = 0; row < flowers.length; row++) {
    var fNames = flowers[row];
      
    for (var i = 0; i < fNames.length; i++) {
        fTemplate.clone().appendTo("#row" + (row + 1)).children()
            .filter("img").attr("src", fNames[i] + ".png").end()
            .filter("label").attr("for", fNames[i]).text(fNames[i]).end()
            .filter("input").attr({name: fNames[i], value: 0})
    }
}

These statements generate three rows of elements that describe flowers. I have generated these elements using loops so I don’t have to get involved in defining templates (although, in general, I would much rather use data templates as described in Chapter 12). Listing 14-19 demonstrates the use of the getScript method to obtain and execute the contents of the myscript.js file.

Listing 14-19.  Using the getScript Method

...
<script type="text/javascript">
    $(document).ready(function () {
        $.getScript("myscript.js");
    });
</script>
...

The getScript method is called when the DOM is ready. The execution of the myscript.js file leads to three rows of flowers elements, as shown by Figure 14-9.

9781430263883_Fig14-09.jpg

Figure 14-9. Using the getScript method to load and execute a JavaScript file

The most important thing to realize when dealing with scripts like this is that the state of the document may change between you initiating the Ajax request and the script statements being executed. Listing 14-20 contains a script from the main document that uses the getScript method but also modifies the DOM before the Ajax request can complete.

Listing 14-20.  Requesting and Executing Scripts with the getScript Method

...
<script type="text/javascript">
    $(document).ready(function () {
        $.getScript("myscript.js");
        $("#row2").remove();
    });
</script>
...

image Tip  The getScript method can be used for any script file, but I find it especially useful for loading and executing scripts that are not central to a web application’s functionality, like tracker or geolocation scripts. The user doesn’t care if I am able to accurately locate his location for my site statistics, but he does care when loading and executing the script makes him wait. By using the getScript method, I can get the information I require without making it annoying. To be clear, I am not suggesting that you do anything that is hidden from the user, only that you defer loading and executing legitimate functionality that the user is unlikely to value more than his time.

In this example I start the Ajax request with the getScript method and then call the remove method to remove the row2 element from the document. This element is used by the myscript.js file to insert some of the new elements.

These elements that would have been added to the row2 element are quietly discarded because the selector for the row2 ID doesn’t match anything in the document. You can see the result in Figure 14-10. Depending on the circumstances, you can view this as a robust design that does its best in the face of document changes or an annoyance that quietly disposes of elements. Either way, it pays not to make too many assumptions about the state of the document in your external JavaScript files.

9781430263883_Fig14-10.jpg

Figure 14-10. The effect of a document change during an Ajax request

Getting JSON Data

The getJSON method obtains JSON data from the server and parses it to create JavaScript objects. This is perhaps the least useful of the three convenience methods because it doesn’t do anything more with the data than the basic get method. Listing 14-21 shows the use of the getJSON method.

Listing 14-21.  Using the getJSON Method

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

The JSON data retrieved from the server is passed to a callback function, much as with the get method that I showed you earlier in the chapter. I have used a data template (described in Chapter 12) to process the data and generate HTML elements from it and then the slice and appendTo method to insert the elements into the document.

image Tip  Notice that you are passed a JavaScript object as the argument to the function. You don’t have to do anything to convert from the JSON format into an object because jQuery takes care of this for you.

Working with JSONP

JSONP is an alternative to CORS and works around the same-origin restriction on Ajax requests. It relies on the fact that the browser will allow you to load JavaScript code from any server, which is how the script element works when you specify a src attribute. To begin with, define a function in the document that will process the data, as follows:

...
function processJSONP(data) {
    //... do something with the data...
}
...

You then make a request to the server where the query string includes your form data and a callback property, set to the name of the function you just defined, as follows:

http://node.jacquisflowershop.com/order? callback=processJSONP&aster=1
    &daffodil=2&rose=2&peony=0&primula=0&snowdrop=0

The server, which needs to understand how JSONP works, generates the JSON data as normal and then creates a JavaScript statement that calls the function you created and passes in the data as an argument, as follows:

processJSONP({"aster":"1","daffodil":"2","rose":"2","total":5})

The server also sets the content type of the response to be text/javascript, which tells the browser that it has received some JavaScript statements and should execute them. This has the effect of invoking the method you defined earlier, passing in the data sent by the server. In this way, you neatly sidestep the same-domain issues without using CORS.

image Caution  Cross-origin requests are restricted for good reason. Don’t use JSONP casually. It can create some serious security problems.

jQuery has convenient support for JSONP. All you have to do is use the getJSON method and specify a URL that contains callback=? in the query string. jQuery creates a function with a random name and uses this when communicating to the server, meaning you don’t have to modify your code at all. Listing 14-22 demonstrates how to make a JSONP request.

Listing 14-22.  Making a JSONP Request Using the getJSON Method

...
<script type="text/javascript">
    $(document).ready(function () {
        $.getJSON("mydata.json", function (data) {
            var tmplElems = $("#flowerTmpl").template({ flowers: data }).filter("*");
            tmplElems.slice(0, 3).appendTo("#row1");
            tmplElems.slice(3).appendTo("#row2");
  
        });
  
        $("button").click(function (e) {
            var formData = $("form").serialize();
            $.getJSON("http://node.jacquisflowershop.com/order?callback=?",
                   formData, 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>
...

Using the Ajax Forms Plug-in

So far, I have been using the built-in jQuery support for Ajax. As I mentioned previously, one of the strengths of jQuery is the ease with which it can be extended to add new functionality and the vibrant world of plug-ins that this leads to. To finish this chapter, I am going to briefly describe a useful form-related plug-in.

If you are interested in using Ajax solely to post form data to a server, then you might like the jQuery Form plug-in, which you can get from www.malsup.com/jquery/form and which I saved to a file called jquery.form.js. The jQuery Form plug-in makes using Ajax on forms extremely simple, as Listing 14-23 demonstrates.

Listing 14-23.  Using the Ajax Forms Plug-in

<!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>
    <script src="jquery.form.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="{{stock}}"
                value="0" required />
        </div>
        {{/flowers}}
    </script>
    <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 () {
  
            $.getScript("myscript.js");
  
            $("form").ajaxForm(function (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>
</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>

In this example, I have added the jquery.form.js script file to the document (this is included in the download for the plug-in) and, in the script element, called the ajaxForm method on the form element. The argument to the ajaxForm method is a callback function, and this provides me with access to the response from the server. This is a neat and simple approach to basic Ajax forms, right down to the fact that the URL to post the form to is taken from the form element itself.

This plug-in does a lot more, and it even includes some support for basic form validation, but if you get to the point where you want to start taking control of your Ajax requests, then I suggest using the low-level Ajax features that I describe in Chapter 15. But for quick and simple situations, this plug-in is convenient and well designed.

Summary

In this chapter, I introduced you to the shorthand and convenience methods that jQuery provides for Ajax. I have shown you how to use the get and post methods to make asynchronous HTTP GET and POST requests, how to work with JSON data, and how to use the convenience methods that deal with specific data types. Along the way, I have shown you the most common Ajax pitfall, explained cross-origin requests, and showed how to deal with them and briefly introduced a jQuery plug-in, which makes it even easier to use Ajax with forms that the shorthand methods. In the next chapter, I’ll show you the low-level API, although you’ll see that it isn’t really that low level and is actually pleasant to use.

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

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