AJAX - The jQuery API - Web Development with jQuery (2015)

Web Development with jQuery (2015)

Part I. The jQuery API

Chapter 7. AJAX

AJAX is the technology that enables you to make arbitrary HTTP requests from JavaScript to obtain new data without the need for reloading a document. AJAX stands for Asynchronous JavaScript and XML. The name is misleading, though, because you don't have to use XML, and your requests don't have to be asynchronous. You can have synchronous requests (a request that causes your code to pause execution until the answer is received from the server) that are in the JSON format. XML is just one of many possible formats that you can use to transmit data from a server to client-side JavaScript.

Using AJAX you can make web documents behave much less like documents and much more like completely self-contained desktop applications. With a web-based application, updates are much easier to propagate because everyone upgrades immediately upon their next visit to the website. No longer do companies worry about maintaining legacy software and users—with a web-based application, everyone is pushed to the latest version. It also becomes easier for a user to access these applications. Because a separate installation is not required on every computer where the application's use is wanted, all that is required is a capable browser on top of moderately capable hardware. Browsers strive to blur the line between desktop applications and web-based applications even more because browsers such as Firefox and Google's Chrome browser make it easier to make a web-based application available as a desktop application via placing an icon on the user's desktop, dock, start menu, or quick-launch bar. In Firefox's case, this functionality is experimental, but in the case of Chrome, the feature is already a reality. Then there is Adobe's AIR runtime, which allows you to develop desktop applications using web standards. Because AIR is built on top of WebKit—which is the rendering engine used in Safari, and iOS, among others—AIR can make sophisticated, complex desktop applications using a robust standards-compliant rendering engine. So, if these companies have anything to say about it, web-based applications will become more popular and increasingly take over certain tasks that desktop applications once served.

Another advantage of web-based application development, which some people may perceive as nefarious, is that web-based applications are immune to piracy, at least in the traditional sense. It's impossible to obtain a web-based application's services without payment because a user can simply be locked out if payment is not made, and use of an application can be limited to a single login session at a time. Up until now, this aspect hasn't been much of a problem, though, because web-based applications are often supported with advertisements that make them free.

Then another advantage still is that you can make a web-based application available to many more operating systems and browsers than you might have otherwise with a self-contained desktop application. You can target Safari, Chrome, Firefox, Internet Explorer, and Opera and reach more than 99 percent of your browsing audience easily. Frameworks like jQuery make this even easier because they eliminate many browser inconsistencies and headaches that might otherwise present as roadblocks to cross-browser development.

There are some disadvantages to web-based applications, though, and a fair discussion should include them. Because a web-based application can change so easily, some users will complain, and some might even refuse to adapt. In addition, the speed of a web-based application has not come close to the same speed offered by native programming. Speed is an issue because JavaScript is an interpreted language, even with most browsers rapidly converting JavaScript to machine code and caching it, which is a huge speed increase. JavaScript (along with (X)HTML and CSS) still has network latency as an issue and still has a disadvantage in being an interpreted language that is processed on-the-fly instead of compiled like most of the desktop applications you use every day. On mobile, the speed issue is still enough of a problem to keep most development from web-based languages and browsers; instead most mobile development is done with a native compiled language such as Objective-C, Swift, Java, or .NET. Finally, as a web-based technology, network or server issues can potentially completely shut down user access to your application, although in some development scenarios a network outage can be worked around.

Nonetheless, AJAX has become a powerful and increasingly essential component of web development; this chapter covers jQuery's built-in methods for making AJAX requests. As you would expect, jQuery takes something that is moderately verbose and complex and boils it down into a much simpler, easier-to-grasp API so that you can start writing AJAX-capable web applications much more quickly.

Making a Server Request

As you're probably already aware, the web works through a protocol called HTTP. When you navigate to a web page, your browser fires off a request to a remote HTTP server that's running Apache, nginx, Microsoft IIS, or some other HTTP server software, using the HTTP communication protocol. AJAX makes it so that you can fire off those HTTP requests programmatically without having to reload the entire web page. After your JavaScript makes a request and receives a response, you can then take that data and manipulate the content that's in front of the user based on the response that you receive. Using the HTTP protocol, there are many ways that you can request data from the server. The most common ways information is transmitted between an HTTP server and client are the GET and POST methods, although there are many more methods that can be implemented as part of a RESTful service. If your server or application is configured to support RESTful calls, you will also have methods such as PUT and DELETE, which I discuss in the section “Sending a REST Request.” Before taking a look at REST, you should first be familiar with the most common methods of transmitting an HTTP request using a simple GET or POST request.

What's the Difference Between GET and POST?

At face value, the GET and POST methods seem identical: Both allow you to request a web page and send data along with that request. Most of the time, for AJAX requests, you want to use the GET method because it is slightly faster from a performance standpoint where AJAX is concerned, but there are other differences that you should be aware of that address semantic differences between the two methods, as well as technical and security differences. The following outlines these differences:

· The GET method is intended for requests that have no tangible, lasting effect on the state of anything. (The HTTP specification calls this type of request safe.) For example, when you make a request and you're simply retrieving data from a database, GET is properly suited for this type of request. If a request results in a change to the database via an insertion, update, or deletion—for example, when managing content or making an order or uploading data—the POST method is best suited. This difference, however, is merely semantic.

· Using the POST method causes a browser to automatically prevent resubmitting a form if the user navigates back to a submitted form using the browser's Back button because the POST method is intended to be used for situations in which data manipulation occurs. This is a technical difference put in place to prevent resubmission of form data. But this automatic prevention is ineffective because you still have to design your server-side programs to account for possible resubmissions…anything that can go wrong, will! Users can be impatient and click the Submit button multiple times or refresh submitted forms, ignoring a browser's warnings. However, the GET method provides no automatic protection against resubmission. This difference is mostly inconsequential to AJAX programming because there is no way for a user to resubmit a POST request without you specifically designing the ability into your program.

· The GET method has a much lower limitation on request length imposed than the POST method. This difference is a technical one that can have an effect on your applications. The limitation of the length a GET request can be varies among browsers, but RFC 2068 states that servers should be cautious about depending on URI lengths greater than 255 bytes. Because GET request data is included as part of the URI (the web page's address), the GET request is actually limited by the length of the URI a browser supports. Internet Explorer can support a URL up to 2,083 characters in length, which is ridiculously long. The POST method, however, theoretically has no limitation on length other than what your server is configured to accept. PHP (a server-side language), for example, is configured to accept a POST request that's 8 MB or less in size, by default. This setting and others, such as how long a script can execute and how much memory it can consume, collectively define how big your POST requests can be in the context of that server-side language. Other server-side languages, no doubt, have similar configuration settings; on the client side, however, a POST request has no hard limitation defined, other than the limits of the client's hardware, network, and server capabilities.

· The POST and GET methods can be encoded differently, again a technical difference. I'm not going to go into this difference in great detail because it is outside the scope of this book. This difference applies when you want to upload files via the POST method; I discuss how to perform a file upload via POST in Chapter 11, “HTML5 Drag and Drop.” As you will see in Chapter 11, however, when doing a file upload via AJAX APIs provided by the browser, the browser takes care of encoding for you.

The distinction between the POST and GET methods is mostly moot when it comes to making a request originating from an AJAX script. Because the user is not involved with the request, the automatic protection portion becomes unnecessary, which leaves only the semantic differences and the limitations in length. For the most part, you can get away with making GET requests for everything, which has been said to have a slight performance advantage over the POST method. Personally, I tend to honor the semantic differences out of simple habit from years of working with forms in client-side programming.

NOTE You can find more information about the performance aspect on the Yahoo Developer website at http://developer.yahoo.com/performance/rules.html.

RESTful Requests

REST, or Representational State Transfer, makes it possible to distinguish between differing actions based on the specified HTTP transport method. So far you've learned about the GET and POST HTTP transport methods. As stated in the last section, distinguishing between GET and POST is mostly semantic because every HTTP server can handle GET and POST. REST is an architectural decision in how you choose to implement your web services. Instead of GET and POST being merely semantically different, you can choose to enforce technical constraints as well, for example, a server-side application that behaves and handles data differently based on whether you use the GET or POST method. In addition to GET and POST, a REST architecture can implement the PUT and DELETE methods. Finally, in addition to those four methods, some implementations may take things even further by defining additional methods, for example, PATCH.

The differences within which HTTP transport method you specify (GET, POST, PUT, or DELETE) can be enforced on the server side. For example, you can send GET, POST, PUT, or DELETE requests to the same URI location with information specified in the body of the request as JSON, and then the server-side application routes the request and executes different code based on which method is specified. Many developers simply use the URL to do the same thing and don't go beyond GET or POST. It has become increasingly more in fashion, however, to use HTTP to clearly define the purpose of a request, as well as to respond to a request using HTTP error codes. Using a REST approach brings your AJAX applications more into the realm of defined standards by making use of the features defined in the HTTP protocol that have been mostly ignored.

In the section “Sending a REST Request,” I present an example of how to send and receive data with jQuery using a REST approach.

Formats Used to Transport Data with an AJAX Request

Although the name implies that you use XML to transport data with an AJAX request, this is entirely optional. In addition to XML, there are two other common ways that data is transmitted from the server to a client-side JavaScript application: JSON (or JavaScript Object Notation) and HTML. You are not limited to these formats, however, because you can conceivably take any data you like from the server and transmit it to the client. These formats are the most popular because JavaScript provides you with tools for working with these types of data. XML can be easily queried using DOM tools and methods, as well as with jQuery's various methods of traversal, filtering, and retrieval. HTML can be sent in incomplete snippets that can be effortlessly inserted into a document using jQuery'shtml() method.

You can also transmit JavaScript from the server, and the JavaScript will be evaluated in the client-side application, executing it and making whatever variables, functions, objects, and so on available.

The JSON format is a subset of the syntax allowed to create JavaScript object literals, and it is therefore a subset of JavaScript. It is considered to be its own format for data transmission, however. Many popular languages have the ability to both read and send JSON-formatted data.

There are potential security issues associated with JSON that you should consider that result from using eval() to execute JavaScript code from the server. eval() should be used only if you are certain that the data being evaluated cannot be manipulated and cannot contain malicious code. For your web application, you should take precautions before using the eval() method to execute anything that has been user-provided because a user can have malicious intentions. Because a portion of your code is available for all to see on the client-side, any user can discover what methods you use to transmit and receive data. If you use JSON to transmit user-supplied data that originates from your input forms, a user can maliciously craft the data submitted in your forms to be executed alongside your JSON-formatted code. One exploit a malicious user can take advantage of in this way would be to execute JavaScript that takes other users' session data and transmits that data back to the malicious user's server. This type of exploit is known as an XSS (Cross-Site Scripting) vulnerability, alternatively known as Cross-Site Scripting Forgery. Because session data is not tied to a user's computer but, instead, relies on long strings of numbers and letters that are mathematically difficult to reproduce, when a malicious user obtains another user's session id, that malicious user can then impersonate other users and steal their sensitive data or log in to your server and obtain privileged information. So great care and thought must be placed into what code is safe to eval() and what code is not.

Making a GET Request with jQuery

Having talked about some of the inner workings of what an AJAX request is, the next topic for discussion is making your first GET request with AJAX using jQuery.

Of course, AJAX is typically used to create dynamic web applications that have a server-side component written in something such as PHP, Java, .NET, Ruby, or whatever you like. The server-side portion of this is outside the scope of this book, so, instead of linking an AJAX request to a server-side application, I link these requests to local documents that provide the same response every time. If you'd like to learn more about the server-side components that are involved, Wrox has an excellent selection of books covering just about every language.

That said, jQuery makes a few methods available that initiate a GET request from a server; the method that you use depends on the data you're getting. The generic method, which you can use to make any type of GET request, is called, easily enough, get(). Each method is a member of the jQuery object, so you'd call the get() method like this: $.get().

Requesting Data Formatted in XML

The first example I demonstrate shows you how to request data from a server that formats the response as XML. The following source code demonstrates an input form for an address in which the country field causes the state field to be dynamically updated when the country selection is changed and the country's flag is changed as well. Each list of states is dynamically fetched from the server using an AJAX request. However, this happens only for three of the country selections—the United States, Canada, and the United Kingdom—because the information is fed from static XML files rather than a database-driven server. If I were to create an XML file for all 239 country options, I would at least change the flag for that country, even if no administrative subdivision similar to a state exists for that country. The following is the HTML portion of Example 7-1:

<!DOCTYPE HTML>

<html lang='en'>

<head>

<meta http-equiv='X-UA-Compatible' content='IE=Edge' />

<meta charset='utf-8' />

<title>Context Menu Example</title>

<script src='../jQuery.js'></script>

<script src='../jQueryUI.js'></script>

<script src='Example 7-1.js'></script>

<link href='Example 7-1.css' rel='stylesheet' />

</head>

<body>

<form action='javascript:void(0);' method='post'>

<fieldset>

<legend>Address</legend>

<div id='addressCountryWrapper'>

<label for='addressCountry'>

<img src='flags/us.png' alt='Country' />

</label>

<select id='addressCountry' size='1' name='addressCountry'>

<option value='0'>Please select a country</option>

<option value='1'>Afghanistan</option>

<option value='2'>Albania</option>

<option value='3'>Algeria</option>

<option value='4'>American Samoa</option>

<option value='5'>Andorra</option>

The long list of countries has been snipped out. The complete file is available as part of this book's free source code download materials available from www.wrox.com/go/webdevwithjquery.

<option value='222'>United Kingdom</option>

<option value='223' selected='selected'>United States</option>

<option value='224'>United States Minor Outlying Islands</option>

<option value='225'>Uruguay</option>

<option value='226'>Uzbekistan</option>

<option value='227'>Vanuatu</option>

<option value='228'>Vatican City State (Holy See)</option>

<option value='229'>Venezuela</option>

<option value='230'>Vietnam</option>

<option value='231'>Virgin Islands (British)</option>

<option value='232'>Virgin Islands (U.S.)</option>

<option value='233'>Wallis and Futuna Islands</option>

<option value='234'>Western Sahara</option>

<option value='235'>Yemen</option>

<option value='236'>Yugoslavia</option>

<option value='237'>Zaire</option>

<option value='238'>Zambia</option>

<option value='239'>Zimbabwe</option>

</select>

</div>

<div>

<label for='addressStreet'>Street Address:</label>

<textarea name='addressStreet'

id='addressStreet'

rows='2'

cols='50'></textarea>

</div>

<div>

<label for='addressCity'>City:</label>

<input type='text' name='addressCity' id='addressCity' size='25' />

</div>

<div>

<label for='addressState'>State:</label>

<select name='addressState' id='addressState'>

</select>

</div>

<div>

<label for='addressPostalCode'>Postal Code:</label>

<input type='text'

name='addressPostalCode'

id='addressPostalCode'

size='10' />

</div>

<div id='addressButtonWrapper'>

<input type='submit'

id='addressButton'

name='addressButton'

value='Save' />

</div>

</fieldset>

</form>

</body>

</html>

The preceding HTML is styled with the following CSS:

body {

font: 16px sans-serif;

}

fieldset {

background: #93cdf9;

border: 1px solid rgb(200, 200, 200);

}

fieldset div {

padding: 10px;

margin: 5px;

}

fieldset label {

float: left;

width: 200px;

text-align: right;

padding: 2px 5px 0 0;

}

div#addressCountryWrapper img {

position: relative;

top: -4px;

}

div#addressButtonWrapper {

text-align: right;

}

Then, the following JavaScript is included in the preceding HTML document:

$(document).ready(

function()

{

$('select#addressCountry').click(

function()

{

$.get(

'Example 7-1/' + this.value + '.xml',

function(xml)

{

// Make the XML query-able with jQuery

xml = $(xml);

// Get the ISO2 value, that's used for the

// file name of the flag.

var iso2 = xml.find('iso2').text();

// Swap out the flag image

$('div#addressCountryWrapper img').attr({

alt : xml.find('name'),

src : 'flags/' + iso2.toLowerCase() + '.png'

});

// Remove all of the options

$('select#addressState').empty();

// Set the states...

xml.find('state').each(

function()

{

$('select#addressState').append(

$('<option/>')

.attr('value', $(this).attr('id'))

.text($(this).text())

);

}

);

// Change the label

$('label[for="addressState"]').text(

xml.find('label').text() + ':'

);

},

'xml'

);

}

);

$('select#addressCountry').click();

}

);

Then for the AJAX requests to succeed, you need to create some XML files for the response content. When you change the country in the <select> element, an AJAX request is sent via the GET method for the file Example 7-1/<addressCountry>.xml, where<addressCountry> is the numeric id of the country selected from the drop-down list. I've prepared XML files for three countries with the ids 38, 222, and 223, those being the respective ids of Canada, the United Kingdom, and the United States. Each XML file looks similar to the following, which is Canada's:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<country>

<name>Canada</name>

<iso2>CA</iso2>

<iso3>CAN</iso3>

<label>Province</label>

<state id='0'> </state>

<state id="66">Alberta</state>

<state id="67">British Columbia</state>

<state id="68">Manitoba</state>

<state id="69">Newfoundland</state>

<state id="70">New Brunswick</state>

<state id="71">Nova Scotia</state>

<state id="72">Northwest Territories</state>

<state id="73">Nunavut</state>

<state id="74">Ontario</state>

<state id="75">Prince Edward Island</state>

<state id="76">Quebec</state>

<state id="77">Saskatchewan</state>

<state id="78">Yukon Territory</state>

</country>

Each XML file is structured identically, providing the country's name, an ISO2 and ISO3 country code, a label, and the list of administrative subdivisions, which I have simply called states, even though that's not always technically correct; Canada's are called provinces and the United Kingdom's are called counties.

The preceding example looks like what you see in Figure 7.1, when you select United Kingdom from the country drop-down.

image

Figure 7.1

In the JavaScript file, things get under way with adding a click event to the <select> element with id name addressCountry. Within the handler for the click event, you begin your AJAX request using jQuery's $.get() method. The first argument specifies the path that you want to request, which is the XML file, dynamically substituting the country id in the filename. The second argument is a callback function that you want to execute when your script has received the server's response, and the third argument is the type of AJAX request that you want to make. For a complete overview of the API of the $.get() method, see Appendix G, “AJAX Methods.”

The callback method that you specified has one argument specified, xml. This variable contains the XML data that the server has sent back. This data is then made into a jQuery object, which makes it much easier to extract data from it:

// Make the XML query-able with jQuery

xml = $(xml);

The next thing to do is to fetch the ISO2 code from the XML document, which is used to fetch the updated flag for the selected country:

// Get the ISO2 value, that's used for the

// file name of the flag.

var iso2 = xml.find('iso2').text();

Just as you would do in a normal HTML document, you can use jQuery's find() method to locate the XML element <iso2> and retrieve its text content via jQuery's text() method. In the context of the three countries I've created XML files for, the iso2 variable would contain CA for Canada, GB for the United Kingdom, or US for the United States. The next step is to set the alt and src attributes of the <img> element referencing the country flag:

// Swap out the flag image

$('div#addressCountryWrapper img').attr({

alt : xml.find('name'),

src : 'flags/' + iso2.toLowerCase() + '.png'

});

The <img> element is located by querying the DOM for a <div> element with an id attribute having the value addressCountryWrapper and then finding the <img> element within. Then the <img> element's src and alt attributes are set using jQuery's attr() method, and the path for the flag is defined, taking into account the structure of files in this book's source code download materials. Then the filename is appended, and the ISO2 code is converted to lowercase because each flag image is named using lowercase characters. This may not be a problem for some servers, such as Windows or some Mac servers that are case-insensitive, but UNIX and Linux servers, including some Mac servers (depending on how the Macs have been formatted), are case-sensitive, and having the incorrect case for the filename would cause the image to fail to load.

The next step is to remove all state options. First, query for the <select> element with the id name addressState, and then call jQuery's empty() method to remove all options.

// Remove all of the options

$('select#addressState').empty();

The next step is to add the administrative subdivisions from the XML file as options. jQuery's find() method locates all the <state> elements in the XML file. Then you enumerate over each <state> element using the each() method.

// Set the states...

xml.find('state').each(

function()

{

Now you create each <option> element and append each element to the <select> element:

$('select#addressState').append(

$('<option/>')

.attr('value', $(this).attr('id'))

.text($(this).text())

);

Because you're working within the callback function provided to the each() method, each <state> element is passed to that callback function as this, just as it is done with (X)HTML. To access jQuery's methods, you wrap this in a call to the jQuery object $(this). You set the value attribute for each <option> element, which will be the unique numeric id passed in each <state> element as the attribute id=”0”, where zero is the unique id. To get to that id, all you have to do is call jQuery's attr() method with the attribute's name as the first argument. Then all that's left to do is set the option's label, which is done with a simple call to jQuery's text() method, which retrieves the text content of the <state> element.

The last item is to set the label for the “state.” Because Canadians use provinces, Britons use counties, and Americans use states, you need to use the right label, which is provided in the XML file as the <label> element. To find the <label> element you want to change, you query the DOM for the <label> element with a for attribute having the value addressState. Then you set that <label> element's text content to the text content of the <label> element from the XML document.

// Change the label

$('label[for="addressState"]').text(

xml.find('label').text() + ':'

);

As you can see with the preceding example, jQuery does not disappoint with its well-thought-out AJAX-handling abilities. With traditional JavaScript and DOM methods, the preceding would have been much more verbose and much more difficult to get working. jQuery's capability to bind itself to an XML response makes parsing and working with XML documents just like working with HTML documents: easy.

NOTE The iTunes-like flags included in the source code download originated from the following website, where you may also obtain higher-quality images:

1. www.bartelme.at/journal/archive/flag_button_devkit/

Sending Data Along with a Request

Let's say in that last example that you were actually working with a database-driven server; in that case, how you would have constructed the request would be slightly different than in the preceding example. Instead of dynamically creating the filename of the XML file you want to retrieve using the country's id, you would instead need to pass that information separately. jQuery accommodates passing data in the $.get() method. In the context of the preceding example, you started out making a call to the $.get() method that looked like this, with the extra code snipped out to make the example easier to understand:

$.get(

'Example 7-1/38.xml',

function(xml)

{

// snip

},

'xml'

);

The first argument is the path of the file you're requesting—this can be any URL value. Typically, you'll want to reference some server-side script that can output data for you. The second argument is the callback function that the server's response XML will be passed to, and the third argument is the type of request being made, which is one of the following strings: 'xml', 'html', 'script', 'json', 'jsonp', or 'text'. This argument is set depending on the type of data that you expect coming back from the server.

When you want to send additional data with the request, another argument is added:

$.get(

'Example 7-1/38.xml', {

countryId : 223,

iso2 : 'US',

iso3 : 'USA',

label : 'State'

},

function(xml)

{

// snip

},

'xml'

);

The new argument comes after the filename and before the function reference, and this is an object literal that contains the data you want to pass along in the GET request to the server. In the preceding example, I've modified the filename to be simply Example 7-1/38.xml, and I've created an object literal with four properties, countryId, iso2, iso3, and label. So, behind the scenes, this modification will cause the request to the server to look like this:

Example%207-1/38.xml?countryId=223&iso2=US&iso3=USA&label=State

jQuery takes the items in the object literal and builds a GET request from them. Because GET requests include data as part of the URL that you are calling, that data gets appended to the end of the URL. The question mark in the URL indicates that what follows is GET request data; then values are passed in name/value pairs, where each name and value is separated by an equals sign. Then if there is more than one value, additional values are appended subsequently by appending an ampersand character to the last name/value pair. Then this data is encoded for transport to the HTTP server. When at the HTTP server, how this data is accessed depends on the server-side language that you're using to read it.

Requesting JSON Formatted Data

This section revisits the example from the last section but this time uses JSON as the format for data transport instead of XML. I could use the same jQuery method, $.get(), to do this and change the last argument from 'xml' to 'json', but jQuery offers another method called $.getJSON() for retrieving JSON-formatted data. This method is just like the $.get() method except that the data format returned by the server is obviously expected to be JSON.

Using JSON as the data transportation format makes the code even leaner and easier to work with than XML, in addition to significantly reducing the size of the response from the server. The following example is the same example that you saw in the last section where when you select Canada, the United States, or the United Kingdom from the drop-down, the flag, administrative subdivisions, and administrative subdivision label all swap out, presenting data relevant to the country you're looking at. The HTML portion remains the same, and just a few modifications are made to the JavaScript portion. This example is available in the source code materials as Example 7-2.

$(document).ready(

function()

{

$('select#addressCountry').click(

function()

{

$.getJSON(

'Example 7-2/' + this.value + '.json',

function(json)

{

// Swap out the flag image

$('div#addressCountryWrapper img').attr({

alt : json.name,

src : 'flags/' + json.iso2.toLowerCase() + '.png'

});

// Remove all of the options

$('select#addressState').empty();

// Set the states...

$.each(

json.states,

function(id, state)

{

$('select#addressState').append(

$('<option/>')

.attr('value', id)

.text(state)

);

}

);

// Change the label

$('label[for="addressState"]').text(

json.label + ':'

);

}

);

}

);

$('select#addressCountry').click();

}

);

In the preceding JavaScript, things function similarly to the example that you saw in the last section where the server response was formatted as XML. However, this time you initiate an AJAX request using the $.getJSON() method instead of the $.get() method.Figure 7.2 shows the results.

These two methods are similar, except that you don't have to specify the last argument, specifying the format of the server response with the $.getJSON() method. Another difference is that you are requesting a file with a .json extension instead of .xml. Also, like in the last example, the file requested depends on which country is selected from the drop-down menu. The JSON object is formatted like so in the file being requested:

{

"name" : "Canada",

"iso2" : "CA",

"iso3" : "CAN",

"label" : "Province",

"states" : {

"0" : " ",

"66" : "Alberta",

"67" : "British Columbia",

"68" : "Manitoba",

"69" : "Newfoundland",

"70" : "New Brunswick",

"71" : "Nova Scotia",

"72" : "Northwest Territories",

"73" : "Nunavut",

"74" : "Ontario",

"75" : "Prince Edward Island",

"76" : "Quebec",

"77" : "Saskatchewan",

"78" : "Yukon Territory"

}

}

image

Figure 7.2

As you can see, the JSON format uses object literal syntax that you're already familiar with in JavaScript. The whole object is wrapped in curly braces but isn't assigned a name, which makes it easy for frameworks such as jQuery to take the JSON-formatted data and assign it directly to an object. In the JavaScript, the preceding JSON was passed to the event handler for the $.getJSON() method as the json argument. All the data that you see in the JSON-formatted document is available inside that json variable. You access the ISO2 information as json.iso2, the label as json.label, and the states array as json.states. Using JSON, you've removed a step that would otherwise be required if you were working with XML data, which is querying the data within the response; with JSON, the data is fed directly to an object and is available immediately. Also note how much leaner the JSON file is compared to the verbose XML document.

Like the $.get() method, if you want to pass data to the server, you can provide that data in the same optional data argument.

$.getJSON(

'Example 7-2/38.json', {

countryId : 223,

iso2 : 'US',

iso3 : 'USA',

label : 'State'

},

function(json)

{

}

);

Making a POST Request

POST requests are identical to GET requests in jQuery, except for the name of the method. Instead of $.get(), you use $.post(). Because a POST method request is reserved for modifying the state of the data in some way, you're probably more often than not going to want to pass some data along with your POST request, and that data will probably come from a form of some kind. jQuery makes it easy to grab form data and pass that along to the server. The method jQuery provides for this is serialize(). The serialize() method takes data for the input elements that you specify (which encompasses <input>, <textarea>, and <select> elements) and processes the values in those fields into a query string. If you do not select any elements for serialization, you can instead select a <form> element and jQuery automatically serializes all <input>, <textarea>, and <select> elements that it finds within the <form> element. The following (Example 7-3) is what the updated JavaScript looks like:

$(document).ready(

function()

{

$('select#addressCountry').click(

function()

{

$.getJSON(

'Example 7-3/' + this.value + '.json',

function(json)

{

// Swap out the flag image

$('div#addressCountryWrapper img').attr({

alt : json.name,

src : 'flags/' + json.iso2.toLowerCase() + '.png'

});

// Remove all of the options

$('select#addressState').empty();

// Set the states...

$.each(

json.states,

function(id, state)

{

$('select#addressState').append(

$('<option/>')

.attr('value', id)

.text(state)

);

}

);

// Change the label

$('label[for="addressState"]').text(

json.label + ':'

);

}

);

}

);

$('select#addressCountry').click();

$('input#addressButton').click(

function(event)

{

event.preventDefault();

$.post(

'Example 7-3/POST.json',

$('form').serialize(),

function(json)

{

if (parseInt(json) > 0)

{

alert('Data posted successfully.');

}

},

'json'

);

}

);

}

);

Make the preceding modifications, load up the new document, and click the Save button. You should see something like Figure 7.3.

image

Figure 7.3

You've added a new event to the <input> element with the id name addressButton. When you click the <input> element, a POST request is initiated using jQuery's $.post() method. Of course, you have no HTTP server set up to transmit this data to, so, instead, you simply reference a static JSON file that lets you know the POST request succeeded at least as far as requesting the specified document. In the second argument to the $.post() method, you supply the data that you want to transmit to the server, just like you can do with the $.get() and $.getJSON() methods that you saw in the previous two sections. However, in addition to supporting an object literal, you can also provide a serialized string of URL-encoded key, value pairs. This is what the serialize() method provides; it encodes key, value pairs and creates a URL-encoded string from them. It searches within the <form> element for all <input>, <textarea>, and <select> elements automatically when you select a <form> element. You can also tell it specifically which <input>, <textarea>, or <select>elements you want to serialize by explicitly selecting those elements. That selection is then passed to jQuery's serialize() method, which finds the right names and values from the various input elements, formatting that data like so:

addressCountry=223

&addressStreet=123+Main+Street

&addressCity=Springfield

&addressState=23

&addressPostalCode=12345

This data appears as one unbroken line when it is passed to serialize(); some line breaks have been added here to make the string more readable. This data is now ready to be posted to the server, so all you have to do is pass this formatted data in the data argument of the $.post() method. This also works for jQuery's other AJAX request methods as well, and jQuery is smart enough to know when you're passing an object literal, as I demonstrated previously, and when you're passing a formatted query string, like you are here. Then, on the server side, all you have to do is access the posted data as you would normally work with POST request data.

Loading HTML Snippets from the Server

In the previous sections, you've seen how to request data from the server using the XML and JSON data transport formats. The other popular way of transporting data from the server to the client asynchronously is via HTML snippets. Using this method, you request small chunks of HTML as you need them without the <html>, <head>, and <body> tags.

The following example (Example 7-4) demonstrates how to load snippets of HTML with jQuery's load() method:

<!DOCTYPE HTML>

<html lang='en'>

<head>

<meta http-equiv='X-UA-Compatible' content='IE=Edge' />

<meta charset='utf-8' />

<title>Folder Tree</title>

<script src='../jQuery.js'></script>

<script src='../jQueryUI.js'></script>

<script src='Example 7-4.js'></script>

<link href='Example 7-4.css' rel='stylesheet' />

</head>

<body>

<div id='folderTree'>

<ul class='folderTree'>

<li>

<div class='folderTreeDirectory folderTreeRoot'

data-id='1'

title='/'>

<span>Macintosh HD</span>

</div>

<ul class='folderTreeDirectoryBranchOn' data-id='1'>

<li class='folderTreeDirectoryBranch'>

<div class='folderTreeDirectory'

data-id='5175'

title='/Applications'>

<div class='folderTreeIcon'></div>

<span>Applications</span>

</div>

<img src='tree/right.png'

class='folderTreeHasChildren'

data-id='5175'

alt='+'

title='Click to expand.' />

<div class='folderTreeBranchWrapper'>

</div>

</li>

<li class='folderTreeDirectoryBranch folderTreeServer'>

<div class='folderTreeDirectory'

data-id='5198'

title='/Library'>

<div class='folderTreeIcon'></div>

<span>Library</span>

</div>

<img src='tree/right.png'

class='folderTreeHasChildren'

data-id='5198'

alt='+'

title='Click to expand.' />

<div class='folderTreeBranchWrapper'></div>

</li>

<li class='folderTreeDirectoryBranch'>

<div class='folderTreeDirectory'

data-id='3667'

title='/System'>

<div class='folderTreeIcon'></div>

<span>System</span>

</div>

<img src='tree/right.png'

class='folderTreeHasChildren'

data-id='5198'

alt='+'

title='Click to expand.' />

<div class='folderTreeBranchWrapper'></div>

</li>

<li class='folderTreeDirectoryBranch'>

<div class='folderTreeDirectory'

data-id='5185'

title='/Users'>

<div class='folderTreeIcon'></div>

<span>Users</span>

</div>

<img src='tree/right.png'

class='folderTreeHasChildren'

data-id='5185'

alt='+'

title='Click to expand.' />

<div class='folderTreeBranchWrapper'></div>

</li>

</ul>

</li>

</ul>

</div>

</body>

</html>

This markup is styled with the following style sheet:

body {

font: 13px "Lucida Grande", Arial, sans-serif;

color: rgb(50, 50, 50);

background: rgb(214, 221, 229);

margin: 0;

padding: 10px;

}

div#folderTree ul {

list-style: none;

padding: 0;

margin: 0;

}

div.folderTreeRoot {

height: 28px;

background: url('tree/internal.png') no-repeat left 1px;

padding: 4px 0 0 28px;

}

li.folderTreeDirectoryBranch {

position: relative;

padding: 0 0 0 20px;

zoom: 1;

}

img.folderTreeHasChildren {

position: absolute;

top: 3px;

left: 0;

}

div.folderTreeIcona {

background: url('tree/folder.png') no-repeat left;

width: 16px;

height: 16px;

margin: 0 5px 0 0;

float: left;

}

div.folderTreeBranchWrapper {

display: none;

}

Then the following JavaScript demonstrates how folders in a tree structure are loaded asynchronously. Each folder is an HTML snippet that loads separately from the server, which makes the initial download much smaller and the overall application much more efficient.

$(document).ready(

function()

{

$('img.folderTreeHasChildren').click(

function()

{

var arrow = 'tree/down.png';

if (!$(this).next().children('ul').length)

{

$(this).next().load(

'Example%207-4/' +

$(this)

.prev()

.data('id') + '.html',

function()

{

$(this)

.show()

.prev()

.attr('src', arrow);

}

);

}

else

{

$(this).next().toggle();

if ($(this).attr('src').indexOf('down') != -1)

{

arrow = 'tree/right.png';

}

$(this).attr('src', arrow);

}

}

);

}

);

All put together, the preceding code looks like Figure 7.4 when it is tested in a browser.

image

Figure 7.4

In the preceding script, a click event is attached to each <img> element in the HTML document. When the user clicks the <img> element, which is a gray arrow, the script first checks to see whether the folder's contents have already been requested, which is done by checking to see if the <img> element's next sibling, the <div> element with class name folderTreeBranchWrapper, has a child <ul> element. Whether that <ul> element exists is determined by the following expression:

if (!$(this).next().children('ul').length)

{

The next() method traverses the selection from the <img> to the <div> element, and the children() method looks at the children of the <div> method. Then the length property determines how many children <ul> elements exist. If there is a <ul> element, that means the folder's contents have already been requested from the server and loaded into the document. If there is not a <ul> element, then the folder's contents are requests from the server.

Loading directly in the document is done based on a selection. In this script, you select the <div> element with the class name folderTreeBranchWrapper, which is done with the call to $(this).next(). this references the <img> element, and next() causes the next sibling element to be selected, which is the <div> element. Then the load() method is chained directly to that selection. By chaining the load() method to the selection, you're telling jQuery where you want the HTML snippet to be inserted in the DOM.

The load() method otherwise works similarly to the other AJAX request methods that jQuery provides: You specify the URL of the document you want to request in the first argument. You can include an optional second argument that includes data that you want to send to the server via a GET request, and the third parameter is a callback function that is executed upon success of the request. Both providing data to send to the server and specifying a callback function are optional—if you like, you can simply call the load()method with only a URL, and that will work just fine as well.

The server responds with a snippet of HTML that is loaded directly into the document. The following is what the HTML snippet being loaded looks like:

<ul data-id="31490s">

<li class="folderTreeDirectoryBranch">

<div class="folderTreeDirectory" data-id="31491s" title="/Users/Shared">

<div class="folderTreeIcon"></div>

<span>Shared</span>

</div>

<img src="tree/right.png"

class="folderTreeHasChildren" data-id="31491s" alt="+"

title="Click to expand." />

<div class="folderTreeBranchWrapper"></div>

</li>

<li class="folderTreeDirectoryBranch folderTreeServer">

<div class="folderTreeDirectory" data-id="698482s" title="/Users/johnappleseed">

<div class="folderTreeIcon"></div>

<span>johnappleseed</span>

</div>

<img src="tree/right.png"

class="folderTreeHasChildren" data-id="698482s" alt="+"

title="Click to expand." />

<div class="folderTreeBranchWrapper"></div>

</li>

</ul>

The preceding is the HTML snippet that is loaded upon clicking the arrow for the /Users folder. I've prepared HTML snippets for each of the top-level folders. In the source code download for this book, each of these is named using a numeric directory id. For example, /Applications has the id 5175, /Library has the id 5198, and so on. Each of these numeric ids is embedded in the data-id attribute of the <div> element with class name folderTreeDirectory that is present in the structure for each folder. Upon requesting the folder contents, the embedded numeric id is extracted with the following:

$(this).prev().data('id')

The preceding starts out at the <img> element, where the click originated, which is the $(this) portion of the code. Then you navigate to the preceding sibling with the prev() method and access its id attribute with data('id'). That's used to construct the filename of the HTML snippet to be loaded, which again wouldn't normally be requested as a static HTML file—for this kind of thing, you want a server-side script to do the heavy lifting. Each HTML snippet is located in a subfolder called Example 7-4.

After the request is made, the following callback function is executed:

function()

{

$(this)

.show()

.prev()

.attr('src', arrow);

}

The callback function is executed within the context of the <div> element with the class name folderTreeBranchWrapper; this refers to that <div> element. By default, all the <div> elements with the class name folderTreeBranchWrapper are hidden by the inclusion ofdisplay: none in the style sheet; calling jQuery's show() method makes the <div> visible. Now all that's left to do is to change the orientation of the arrow from pointing right to pointing down to indicate that the folder is open, which is what the second bit of code in the callback function does. It changes the image referenced in the src attribute of the <div> element's preceding sibling, which is the <img> element housing the arrow.

That leaves what happens if the folder is already loaded:

}

else

{

$(this).next().toggle();

if ($(this).attr('src').indexOf('down') != -1)

{

arrow = 'tree/right.png';

}

$(this).attr('src', arrow);

}

If the folder already exists, you want to toggle the display of the folder on and off with each click of the arrow. The call to $(this).next().toggle() does exactly that: If the <div> element is visible, it's made invisible, and vice versa. The second bit of code toggles the orientation of the arrow by toggling between the right.png and down.png images.

Dynamically Loading JavaScript

Another useful and innovative feature of jQuery is its capability to dynamically and asynchronously load JavaScript documents using its AJAX API. As covered in Chapter 1, “Introduction to jQuery,” it is a recommended best practice to split JavaScript development into smaller, easier-to-digest modules that have narrowly focused tasks. Another technique that goes hand-in-hand with modular JavaScript development is loading the minimal required JavaScript at the initial page load and dynamically loading additional JavaScript via AJAX as it is needed to save page load time and to make applications more responsive.

In addition to modular JavaScript development, another reason you may want to load JavaScript via AJAX is to have JavaScript that changes dynamically, depending on user actions, or when you need to load more-complex applications that vary depending on user input or context.

Whatever use you find for this functionality, in this section, I walk you through the API that jQuery provides for loading JavaScript via its AJAX interface using the $.getScript() method. The following example demonstrates how to load the entire jQuery UI API asynchronously and then uses that API to produce an animation that transitions between two colors. This is demonstrated in the following document, Example 7-5:

<!DOCTYPE HTML>

<html lang='en'>

<head>

<meta charset='utf-8' />

<title>November 2013</title>

<script type='text/javascript' src='../jQuery.js'></script>

<script type='text/javascript' src='Example 7-5.js'></script>

<link type='text/css' href='Example 7-5.css' rel='stylesheet' />

</head>

<body>

<table class="calendarMonth" data-year="2013" data-month="11">

<thead>

<tr class="calendarHeading">

<th colspan="7">

<span class="calendarMonth">November</span>

<span class="calendarDay"></span>

<span class="calendarYear">2013</span>

</th>

</tr>

<tr class="calendarWeekdays">

<th>Sunday</th>

<th>Monday</th>

<th>Tuesday</th>

<th>Wednesday</th>

<th>Thursday</th>

<th>Friday</th>

<th>Saturday</th>

</tr>

</thead>

<tbody>

<tr>

<td class="calendarLastMonth">27</td>

<td class="calendarLastMonth">28</td>

<td class="calendarLastMonth">29</td>

<td class="calendarLastMonth">30</td>

<td class="calendarLastMonth calendarLastMonthLastDay">31</td>

<td class="calendarFirstDay">1</td>

<td>2</td>

</tr>

<tr>

<td>3</td>

<td>4</td>

<td>5</td>

<td>6</td>

<td>7</td>

<td>8</td>

<td>9</td>

</tr>

<tr>

<td>10</td>

<td>11</td>

<td>12</td>

<td>13</td>

<td>14</td>

<td>15</td>

<td>16</td>

</tr>

<tr>

<td>17</td>

<td>18</td>

<td>19</td>

<td class="calendarToday">20</td>

<td>21</td>

<td>22</td>

<td>23</td>

</tr>

<tr>

<td>24</td>

<td>25</td>

<td>26</td>

<td>27</td>

<td>28</td>

<td>29</td>

<td class="calendarLastDay">30</td>

</tr>

<tr>

<td colspan="7" class="calendarEmptyWeek"></td>

</tr>

</tbody>

</table>

</body>

</html>

The preceding HTML is styled with the following style sheet:

html,

body {

width: 100%;

height: 100%;

}

body {

font: 14px Helvetica, Arial, sans-serif;

margin: 0;

padding: 0;

color: rgb(128, 128, 128);

}

table.calendarMonth {

table-layout: fixed;

width: 100%;

height: 100%;

border-collapse: collapse;

empty-cells: show;

}

table.calendarMonth tbody {

user-select: none;

-webkit-user-select: none;

-moz-user-select: none;

-ms-user-select: none;

}

table.calendarMonth th {

font-weight: 200;

border: 1px solid rgb(224, 224, 224);

padding: 10px;

}

tr.calendarHeading th {

font: 24px Helvetica, Arial, sans-serif;

}

table.calendarMonth td {

border: 1px solid rgb(224, 224, 224);

vertical-align: top;

padding: 10px;

}

td.calendarLastMonth,

td.calendarNextMonth {

color: rgb(204, 204, 204);

background: rgb(244, 244, 244);

}

td.calendarDaySelected {

background: yellow;

}

tr.calendarWeekSelected {

background: lightyellow;

}

td.calendarToday {

background: gold;

}

Then the following JavaScript is applied:

$(document).ready(

function()

{

$.getScript(

'../jQueryUI.js',

function()

{

$('table.calendarMonth td:not(td.calendarLastMonth,

td.calendarNextMonth)').click(

function()

{

if ($(this).css('background-color') != 'rgb(200, 200, 200)')

{

$(this).animate({

'background-color' : 'rgb(200, 200, 200)'

},

1000

);

}

else

{

$(this).animate({

'background-color' : 'rgb(255, 255, 255)'

},

1000

);

}

}

);

}

);

}

);

The JavaScript demonstrates how an external script is loaded via jQuery's $.getScript() method. The $.getScript() method takes two arguments: The path to the script that you want to load, and like jQuery's other AJAX request method, it also allows a callback function, which is executed when the script has been loaded and successfully executed.

Figure 7.5 shows a snapshot after the animation that takes place when you click a day in the calendar. The background-color animates from white to rgb(200, 200, 200) (a shade of gray) and when clicked again it animates from rgb(200, 200, 200) back to white, orrgb(255, 255, 255). jQuery animations are covered in more detail in Chapter 8, “Animation and Easing Effects.”

image

Figure 7.5

The script that you load for the example, jQueryUI.js, along with the source code for the example, is available with this book's source code download materials at www.wrox.com/go/webdevwithjquery.

AJAX Events

This section covers what jQuery calls AJAX events. AJAX events are milestones that occur during an AJAX request that can give you feedback about the status of your request or allow you to execute code when each milestone occurs. Examples of milestones are when a request starts, when a request stops, when a request has been sent, when a request has failed, when a request is completed, and when a request is completely successful. I don't go into exhaustive detail about each of these events, but Appendix G has a full listing of all the AJAX methods, properties, and AJAX events supported by jQuery.

One example is how to show an activity indicator while some remote content is being fetched. An activity indicator is an animation that shows the user that something is happening, but it gives the user an indication to wait for that something to happen. There are three ways to do this. Two methods enable you to globally set AJAX events for all AJAX requests using jQuery's AJAX methods, and there is one way to set AJAX events per individual request using jQuery's ajax() method. This section describes how to make a loading message using each of these methods.

Adding jQuery AJAX events globally is easy—all you have to do is call jQuery's ajaxSetup() method. First, you need an activity indicator to show that something is taking place. Typically, an animated GIF is good enough to get the job done. In the following snippet,Example 7-6, from the folder tree example that I presented previously, I've added an animated GIF to display while activity is taking place:

<!DOCTYPE HTML>

<html lang='en'>

<head>

<meta http-equiv='X-UA-Compatible' content='IE=Edge' />

<meta charset='utf-8' />

<title>Folder Tree</title>

<script src='../jQuery.js'></script>

<script src='../jQueryUI.js'></script>

<script src='Example 7-6.js'></script>

<link href='Example 7-6.css' rel='stylesheet' />

</head>

<body>

<div id='folderTree'>

<ul class='folderTree'>

<li>

<div class='folderTreeDirectory folderTreeRoot'

data-id='1'

title='/'>

<span>Macintosh HD</span>

</div>

<ul class='folderTreeDirectoryBranchOn' data-id='1'>

<li class='folderTreeDirectoryBranch'>

<div class='folderTreeDirectory'

data-id='5175'

title='/Applications'>

<div class='folderTreeIcon'></div>

<span>Applications</span>

</div>

<img src='tree/right.png'

class='folderTreeHasChildren'

data-id='5175'

alt='+'

title='Click to expand.' />

<div class='folderTreeBranchWrapper'>

</div>

</li>

<li class='folderTreeDirectoryBranch folderTreeServer'>

<div class='folderTreeDirectory'

data-id='5198'

title='/Library'>

<div class='folderTreeIcon'></div>

<span>Library</span>

</div>

<img src='tree/right.png'

class='folderTreeHasChildren'

data-id='5198'

alt='+'

title='Click to expand.' />

<div class='folderTreeBranchWrapper'></div>

</li>

<li class='folderTreeDirectoryBranch'>

<div class='folderTreeDirectory'

data-id='3667'

title='/System'>

<div class='folderTreeIcon'></div>

<span>System</span>

</div>

<img src='tree/right.png'

class='folderTreeHasChildren'

data-id='5198'

alt='+'

title='Click to expand.' />

<div class='folderTreeBranchWrapper'></div>

</li>

<li class='folderTreeDirectoryBranch'>

<div class='folderTreeDirectory'

data-id='5185'

title='/Users'>

<div class='folderTreeIcon'></div>

<span>Users</span>

</div>

<img src='tree/right.png'

class='folderTreeHasChildren'

data-id='5185'

alt='+'

title='Click to expand.' />

<div class='folderTreeBranchWrapper'></div>

</li>

</ul>

</li>

</ul>

</div>

<div id='folderActivity'>

<img src='tree/activity.gif' alt='Activity Indicator' />

</div>

</body>

</html>

Then, some CSS is added to the example to put the activity indicator in the lower-right part of the window.

body {

font: 13px "Lucida Grande", Arial, sans-serif;

color: rgb(50, 50, 50);

background: rgb(214, 221, 229);

margin: 0;

padding: 10px;

}

div#folderTree ul {

list-style: none;

padding: 0;

margin: 0;

}

div.folderTreeRoot {

height: 28px;

background: url('tree/internal.png') no-repeat left 1px;

padding: 4px 0 0 28px;

}

li.folderTreeDirectoryBranch {

position: relative;

padding: 0 0 0 20px;

zoom: 1;

}

img.folderTreeHasChildren {

position: absolute;

top: 3px;

left: 0;

}

div.folderTreeIcon {

background: url('tree/folder.png') no-repeat left;

width: 16px;

height: 16px;

margin: 0 5px 0 0;

float: left;

}

div.folderTreeBranchWrapper {

display: none;

}

div#folderActivity {

position: absolute;

bottom: 5px;

right: 5px;

display: none;

}

Then finally, the JavaScript is modified so that the activity indicator is dynamically revealed when an AJAX request takes place and hidden when the request concludes:

$(document).ready(

function()

{

$.ajaxSetup({

beforeSend : function(event, request, options)

{

$('div#folderActivity').show();

},

success : function(response, status, request)

{

$('div#folderActivity').hide();

},

error : function(request, status, error)

{

$('div#folderActivity').hide();

}

});

$('img.folderTreeHasChildren').click(

function()

{

var arrow = 'tree/down.png';

if (!$(this).next().children('ul').length)

{

$(this).next().load(

'Example%207-6/' +

$(this)

.prev()

.data('id') + '.html',

function()

{

$(this)

.show()

.prev()

.attr('src', arrow);

}

);

}

else

{

$(this).next().toggle();

if ($(this).attr('src').indexOf('down') != -1)

{

arrow = 'tree/right.png';

}

$(this).attr('src', arrow);

}

}

);

}

);

This modification looks like what you see in Figure 7.6, when you make an AJAX request. Because you're requesting a file from your own local computer, the activity indicator will be revealed and hidden almost instantaneously. So this technique is obviously better suited for requesting content from a remote server where there may be some latency.

image

Figure 7.6

In the JavaScript, you make a call to $.ajaxSetup() to define events called beforeSend, success, and error. Each of these events is defined inside a JavaScript object literal that is passed to the $.ajaxSetup() method. By attaching a callback function to the beforeSendproperty, you are telling jQuery to execute the specified function before every AJAX request. In this case, you cause the activity indicator to be displayed by calling jQuery's show() method. Then, after the request has completed successfully, you hide the activity indicator by attaching a callback function to the success and error events, which, in turn, is executed upon a successful or failed request. (Another way to do this is to attach to the AJAX complete event, which executes when a request is completed, whether it was successful or failed.) These are but a few of the properties that you can specify using this method to define AJAX defaults globally for jQuery. All the options that can be specified here are outlined in detail in Appendix G.

You are not, of course, limited to this use of jQuery's AJAX events. jQuery's AJAX events can also modify the HTTP headers that will be used in the request or to do other low-level things with jQuery's AJAX API.

Using AJAX Event Methods

The preceding example defines events globally using the $.ajaxSetup() method. In the following example, Example 7-7, I demonstrate how to do the same using individual jQuery AJAX event methods. Only the script for the preceding example, Example 7-6, has been modified:

$(document).ready(

function()

{

$(document)

.ajaxSend(

function(event, request, options)

{

if (decodeURI(options.url).indexOf('Example 7-7') != -1)

{

$('div#folderActivity').show();

}

}

)

.ajaxSuccess(

function(response, status, request)

{

if (decodeURI(options.url).indexOf('Example 7-7') != -1)

{

$('div#folderActivity').hide();

}

}

)

.ajaxError(

function(request, status, error)

{

if (decodeURI(options.url).indexOf('Example 7-7') != -1)

{

$('div#folderActivity').hide();

}

}

);

$('img.folderTreeHasChildren').click(

function()

{

var arrow = 'tree/down.png';

if (!$(this).next().children('ul').length)

{

$(this).next()

.load(

'Example%207-7/' +

$(this)

.prev()

.data('id') + '.html',

function()

{

$(this)

.show()

.prev()

.attr('src', arrow);

}

);

}

else

{

$(this).next().toggle();

if ($(this).attr('src').indexOf('down') != -1)

{

arrow = 'tree/right.png';

}

$(this).attr('src', arrow);

}

}

);

}

);

The preceding modification gives you the same outcome demonstrated by Example 7-6, only this time the functions that reveal and hide the activity indicator are attached by using jQuery's AJAX event methods instead of the $.ajaxSetup() method. This example takes things one step further by taking a look at the options.url property, decoding URI encoded characters with a call to decodeURI(), and then limiting the application of the activity indicator based on the URI of the AJAX request. Things are set up similarly toExample 7-6; otherwise, you moved the callback function for the beforeSend property to inside the call to the ajaxSend() method, and the callback function for the success property to inside the call to the ajaxSuccess() method, and finally the call for the error property to inside the call to the ajaxError() method. And those methods are, of course, chainable like most of jQuery's other methods, but these methods must be applied to the document object and cannot be attached to just any HTML element object.

Attaching AJAX Events to Individual Requests

The last way that you can attach events is via a call to jQuery's more low-level ajax() method. The ajax() method is used internally, within jQuery, to construct AJAX requests for jQuery's other AJAX request methods, like $.get(), $getJSON(), $.post(), and so on. jQuery's $.ajax() method gives you the ability to set as many low-level AJAX request options as you like. Example 7-8 demonstrates how to use $.ajax() to mimic the same results as the preceding two examples:

$(document).ready(

function()

{

$('img.folderTreeHasChildren').click(

function()

{

var arrow = 'tree/down.png';

if (!$(this).next().children('ul').length)

{

var tree = $(this);

var file = (

$(this)

.prev()

.data('id') + '.html'

);

$.ajax({

beforeSend : function(event, request, options)

{

$('div#folderActivity').show();

},

success : function(response, status, request)

{

$('div#folderActivity').hide();

tree.attr('src', arrow)

.next()

.html(response)

.show();

},

error : function(request, status, error)

{

$('div#folderActivity').hide();

},

url : 'Example%207-8/' + file,

dataType : 'html'

});

}

else

{

$(this).next().toggle();

if ($(this).attr('src').indexOf('down') != -1)

{

arrow = 'tree/right.png';

}

$(this).attr('src', arrow);

}

}

);

}

);

The preceding example is functionally identical to the last two examples that you've seen in this section. Just like those other two examples, you are requesting the contents of each folder with each AJAX request, and you're showing an activity indicator that appears while the AJAX request is taking place and is hidden when it completes. Because the $.ajax() method works by calling that method of the jQuery object directly, you have to change your approach from using the load() method. First, because you want to load HTML, you need to remember what element you want to load that HTML into.

var tree = $(this);

$(this) is assigned to a variable called tree so that you can reference the variable tree from within the callback functions that you assign to the various options of the $.ajax() method. If you remember from Example 7-4, this refers to the <img> element containing the arrows that appear beside each folder. The $.ajax() method takes various options defined as an object literal, which are documented in Appendix G. You again define the beforeSend, success, and error options that contain functions that reveal and hide the activity indicator, but this time in the context of the AJAX request that you're making instead of globally.

If your request were successful, the rest of the code carries on like the code from Example 7-4. The <img> element is contained in the variable tree. The src of the <img> element is changed to 'tree/down.png'. The response variable in the success method contains the HTML text content of the response containing the subfolders. If this were an XML request, you'd be working with an XML object; if it were JSON, you'd be working with a JSON object. The HTML snippet is loaded into the next sibling <div> element that appears after the <img> element; then that <div> element is made visible with the show() method.

success : function(response, status, request)

{

$('div#folderActivity').hide();

tree.attr('src', arrow)

.next()

.html(response)

.show();

},

jQuery's $.ajax() method allows for a great deal of request customization, which should be used when the other AJAX methods just don't provide the options that you need.

Sending a REST Request

The last example of using the $.ajax() method that I present is how to make and send a REST request. Sending a REST request with jQuery is straightforward; you must configure the type, contentType, dataType, and data properties to set up the REST call. Otherwise, your server must also be properly configured to receive calls to a REST service. This will include setting the Access-Control-Allow-Methods HTTP header on your server, which will allow HTTP request methods other than GET and POST. Properly setting up and configuring a web server to deliver a REST service is outside the scope of this book. You can, however, examine what is required on the client side for such a request utilizing jQuery's $.ajax() method. This is demonstrated in the following document, Example 7-9:

<!DOCTYPE HTML>

<html lang='en'>

<head>

<meta http-equiv='X-UA-Compatible' content='IE=Edge' />

<meta charset='utf-8' />

<title>REST Requests</title>

<script src='../jQuery.js'></script>

<script src='../jQueryUI.js'></script>

<script src='Example 7-9.js'></script>

<link href='Example 7-9.css' rel='stylesheet' />

</head>

<body>

<form action='javascript:void(0);' method='post'>

<fieldset>

<legend>Address</legend>

<div id='addressCountryWrapper'>

<label for='addressCountry'>

<img src='flags/us.png' alt='Country' />

</label>

<select id='addressCountry' size='1' name='addressCountry'>

<option value='0'>Please select a country</option>

<option value='1'>Afghanistan</option>

<option value='2'>Albania</option>

<option value='3'>Algeria</option>

<option value='4'>American Samoa</option>

<option value='5'>Andorra</option>

The long list of countries has been snipped out. The complete file is available as part of this book's free source code download materials.

<option value='222'>United Kingdom</option>

<option value='223' selected='selected'>United States</option>

<option value='224'>United States Minor Outlying Islands</option>

<option value='225'>Uruguay</option>

<option value='226'>Uzbekistan</option>

<option value='227'>Vanuatu</option>

<option value='228'>Vatican City State (Holy See)</option>

<option value='229'>Venezuela</option>

<option value='230'>Vietnam</option>

<option value='231'>Virgin Islands (British)</option>

<option value='232'>Virgin Islands (U.S.)</option>

<option value='233'>Wallis and Futuna Islands</option>

<option value='234'>Western Sahara</option>

<option value='235'>Yemen</option>

<option value='236'>Yugoslavia</option>

<option value='237'>Zaire</option>

<option value='238'>Zambia</option>

<option value='239'>Zimbabwe</option>

</select>

</div>

<div>

<label for='addressStreet'>Street Address:</label>

<textarea name='addressStreet'

id='addressStreet'

rows='2'

cols='50'></textarea>

</div>

<div>

<label for='addressCity'>City:</label>

<input type='text' name='addressCity' id='addressCity' size='25' />

</div>

<div>

<label for='addressState'>State:</label>

<select name='addressState' id='addressState'>

</select>

</div>

<div>

<label for='addressPostalCode'>Postal Code:</label>

<input type='text'

name='addressPostalCode'

id='addressPostalCode'

size='10' />

</div>

<div id='addressButtonWrapper'>

<input type='submit'

id='addressButton'

name='addressButton'

value='Save' />

</div>

</fieldset>

</form>

</body>

</html>

The preceding markup is combined with the following style sheet:

body {

font: 12px "Lucida Grande", Arial, sans-serif;

color: rgb(50, 50, 50);

margin: 0;

padding: 0 10px;

}

fieldset {

background: orange;

border: 1px solid rgb(200, 200, 200);

}

legend {

position: relative;

top: 13px;

font-size: 16px;

}

fieldset div {

padding: 5px;

margin: 3px;

clear: left;

}

fieldset label {

float: left;

width: 200px;

text-align: right;

padding: 2px 5px 0 0;

}

div#addressCountryWrapper img {

position: relative;

top: -4px;

}

div#addressButtonWrapper {

text-align: right;

}

Finally, the following JavaScript demonstrates a REST request using the ADD method:

$(document).ready(

function()

{

$('select#addressCountry').click(

function()

{

$.getJSON(

'Example 7-9/' + this.value + '.json',

function(json)

{

// Swap out the flag image

$('div#addressCountryWrapper img').attr({

alt : json.name,

src : 'flags/' + json.iso2.toLowerCase() + '.png'

});

// Remove all of the options

$('select#addressState').empty();

// Set the states...

$.each(

json.states,

function(id, state)

{

$('select#addressState').append(

$('<option/>')

.attr('value', id)

.text(state)

);

}

);

// Change the label

$('label[for="addressState"]').text(

json.label + ':'

);

}

);

}

);

$('select#addressCountry').click();

$('input#addressButton').click(

function(event)

{

event.preventDefault();

var data = {

country : $('select#addressCountry').val(),

street : $('textarea#addressStreet').val(),

city : $('input#addressCity').val(),

state : $('select#addressState').val(),

postalCode : $('input#addressPostalCode').val()

};

$.ajax({

url : 'Example%207-9/ADD.json',

contentType : "application/json; charset=utf-8",

type : 'ADD',

dataType : 'json',

data : JSON.stringify(data),

success : function(json, status, request)

{

if (parseInt(json) > 0)

{

alert('Data added successfully.');

}

},

error : function(request, status)

{

}

});

}

);

}

);

If you use this example with a properly configured server, you will note that the request is sent to the server along with a payload of JSON formatted data that can be decoded into an object on the server side. The screen shot shows submitting data via the ADD method to my own server, which has been configured with the Access-Control-Allow-Methods HTTP header, making it possible to submit REST requests. Figure 7.7 shows the address form.

image

Figure 7.7

Figure 7.8 shows Safari's web inspector, which shows the request data sent along to the server.

image

Figure 7.8

Figure 7.9 shows a closer look at the web inspector's detailed view of the HTTP request and the server's response.

This example shows you that setting up a REST request on the client side is easy, but you need to have the right server-side configuration to complete an implementation. Implementing a REST service call is as easy as pointing your client-side app to a server configured to handle the additional request methods that REST provides. You need to set up the request to specify the right method, such as GET, POST, DELETE, or ADD, which is done using jQuery's type property with the $.ajax() method. Then if you intend to pass JSON data between the server and client, you also need to set the contentType, dataType, and data properties. The contentType tells the server what to expect in the body of the request. The dataType property tells jQuery what type of data to expect in the server's response, and the data property passes along the data to be placed in the body of the HTTP request to the server. The data assigned to the data property will then be accessible on the server side. In this example, the data is passed with JSON formatting. On the server side, you would then need to decode the JSON formatted data into an object.

image

Figure 7.9

Summary

This chapter took you on a tour of jQuery's built-in AJAX capabilities. You saw the differences between a GET and a POST HTTP request, learning that a GET request has a limit on its length, in addition to being semantically suited for requests that result in no lasting modification or effect on the server. POST requests, in contrast, should be reserved for requests that shouldn't be arbitrarily repeated and do have some kind of lasting impact on the server. In terms of AJAX, GET requests have a slight performance advantage. In addition, you can take advantage of REST to make your requests even more semantic, adding method verbs such as ADD or DELETE to the existing GET and POST.

jQuery offers the $.get() method to make GET requests and the $.post() method to make POST requests. When you work with XML, jQuery makes it super-easy to extract information from an XML document by giving you full access to jQuery's various selection methods for querying your XML responses. The JSON format is easier to work with, but extra care must be taken to ensure that you don't make yourself vulnerable to a cross-site forgery. For working with JSON data, jQuery offers the $.getJSON() method.

You can use the load() method to insert HTML snippets requested from the server into elements that you select with jQuery.

I described how to load script asynchronously with jQuery's $.getScript() method. This was demonstrated by loading the jQuery UI API on demand, which was applied to the calendar you made in Example 7-5, and provided a nifty animated effect when you clicked the various days in the calendar.

I talked about jQuery's AJAX events and described the different ways you can use AJAX events to add an activity indicator to the folder tree example, creating Example 7-6, Example 7-7, and Example 7-8 demonstrating the different ways that AJAX events can be utilized.

Finally, I demonstrated how to use jQuery's $.ajax() method to create a request to a server implementing REST services.

Exercises

1. For an AJAX request, is there any difference between a GET and a POST HTTP request?

2. What does a REST service provide?

3. How would you provide extra data with a request using jQuery's $.get() method?

4. How would you access a JSON object in a callback function provided to the $.getJSON() method?

5. Given the following XML, how would you access the contents of the <response> element, assuming you used jQuery's $.get() method to request the XML document?

6. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<response>Yes!</response>

7. If you wanted to load an HTML snippet into a selection of elements, what jQuery method would you use?

8. In the following JavaScript, describe the purpose of each property's callback function:

9. $.ajaxSetup({

10. beforeSend : function()

11. {

12. },

13. success : function()

14. {

15. },

16. error : function()

17. {

18. },

19. complete : function()

20. {

21. }

});

22.If you wanted to attach AJAX events to apply only in the context of an individual AJAX request, rather than globally, what methods does jQuery provide to attach events in this way?

23.What jQuery method would you use if you wanted to get the value of every input element within a form?

24.How would you go about implementing a client-side request to a REST service providing the DELETE method where you pass a JSON object along to the server within the request? Describe the configuration you would need to accomplish this and then provide example code.