Ø
The technologies involved
Ø
Loading data on demand
Ø
Choosing a data format
Ø
Passing data to the server
Ø
Keeping an eye on the request
Technically, AJAX is an acronym standing for
Asynchronous JavaScript and XML. The technologies involved in an AJAX
solution include:
Ø
JavaScript, to capture interactions with the user or other
browser-related events
Ø
The XMLHttpRequest object, which allows requests to be made
to the server without interrupting other browser tasks
Ø
XML files on the server, or often other similar data
formats such as HTML or JSON
Ø
More JavaScript, to interpret the data from the server and
present it on the page
Many frameworks have sprung up to assist developers in
taming it, because of the inconsistencies in the browsers' implementations of
the XMLHttpRequest object; jQuery is no exception.
Loading data on demand
Underneath all the hype and trappings, AJAX is just a
means of loading data from the server to the web browser, or
client, without a visible page refresh. This data can take many forms,
and we have many options for what to do with it when it arrives. We'll see this
by performing the same basic task in many ways.
We are going to build a page that displays entries from
a dictionary, grouped by the starting letter of the dictionary entry. The HTML
defining the content area of the page will look like this:
<div id="dictionary"> </div>
Yes, really! Our page will have no content to begin
with. We are going to use jQuery's various AJAX methods to populate this
<div> with dictionary entries.
<div class="letters"> <div class="letter"
id="letter-a"> <h3><a href="#">A</a></h3>
</div> <div class="letter" id="letter-b">
<h3><a href="#">B</a></h3> </div>
<div class="letter" id="letter-c"> <h3><a
href="#">C</a></h3> </div> <div class="letter"
id="letter-d"> <h3><a href="#">D</a></h3>
</div> </div>
As always, a real-world implementation should use
progressive enhancement to make the page function without requiring JavaScript.
Here, to simplify our example, the links do nothing until we add behaviors to
them with jQuery.
As always, a real-world implementation should use
progressive enhancement to make the page function without requiring
JavaScript. Here, to simplify our example, the links do nothing until we add
behaviors to them with jQuery.
Adding a few CSS rules, we get a page that looks like
this:
Now we can focus on getting content onto the page.
Appending HTML
AJAX applications are often no more than a request for a
chunk of HTML. This technique, sometimes referred to as AHAH
(Asynchronous HTTP and HTML), is almost trivial to implement with jQuery.
First we need some HTML to insert, which we'll place in a file called
a.html alongside our main document. This secondary HTML file begins:
<div class="entry"> <h3
class="term">ABDICATION</h3> <div
class="part">n.</div> <div class="definition"> An act
whereby a sovereign attests his sense of the high temperature of the
throne. <div class="quote"> <div
class="quote-line">Poor Isabella's Dead, whose
abdication</div> <div class="quote-line">Set all tongues
wagging in the Spanish nation.</div> <div
class="quote-line">For that performance 'twere unfair to
scold her:</div> <div class="quote-line">She wisely left a
throne too hot to hold her.</div> <div
class="quote-line">To History she'll be no royal riddle
—</div> <div class="quote-line">Merely a plain
parched pea that jumped the griddle.</div>
<div class="quote-author">G.J.</div> </div>
</div> </div>
<div class="entry"> <h3
class="term">ABSOLUTE</h3> <div
class="part">adj.</div> <div class="definition">
Independent, irresponsible. An absolute monarchy is one in which the
sovereign does as he pleases so long as he pleases the assassins. Not
many absolute monarchies are left, most of them having been replaced
by limited monarchies, where the sovereign's power for evil (and
for good) is greatly curtailed, and by republics, which are
governed by chance. </div> </div>
The page continues with more entries in this HTML
structure. Rendered on its own, this page is quite plain:
Note that a.html is not a true HTML document; it
contains no <html>, <head>, or <body>,
all of which are normally required. We usually call such a file a snippet
or fragment; its only purpose is to be inserted into another HTML
document, which we'll accomplish now:
$(document).ready(function() { $('#letter-a
a').click(function() { $('#dictionary').load('a.html'); return
false; }); });
The .load() method does all our heavy lifting for
us! We specify the target location for the HTML snippet by using a normal jQuery
selector, and then pass the URL of the file to be loaded as a parameter to the
method. Now, when the first link is clicked, the file is loaded and placed
inside <div id="dictionary">. The browser will render the new HTML
as soon as it is inserted:
Note that the HTML is now styled, whereas before it was
plain. This is due to the CSS rules in the main document; as soon as the new
HTML snippet is inserted, the rules apply to its tags as well.
When testing this example, the dictionary definitions
will probably appear instantaneously when the button is clicked. This is a
hazard of working on our applications locally; it is hard to account for delays
in transferring documents across the network. Suppose we added an alert box to
display after the definitions are loaded:
$(document).ready(function() { $('#letter-a
a').click(function() { $('#dictionary').load('a.html');
alert('Loaded!'); return false; }); });
However, when this particular code is tested on a
production web server, the alert will quite possibly have come and gone before
the load has completed, due to network lag. This happens because all AJAX calls
are by default asynchronous. Otherwise, we'd have to call it SJAX, which
hardly has the same ring to it! Asynchronous loading means that once the HTTP
request to retrieve the HTML snippet is issued, script execution immediately
resumes without waiting. Sometime later, the browser receives the response from
the server and handles it. This is generally desired behavior; it is unfriendly
to lock up the whole web browser while waiting for data to be retrieved.
If actions must be delayed until the load has been
completed, jQuery provides a callback for this. An example will be
provided below.
Working with JavaScript objects
Pulling in fully-formed HTML on demand is very
convenient, but there are times when we want our script to be able to do some
processing of the data before it is displayed. In this case, we need to retrieve
the data in a structure that we can traverse with JavaScript.
With jQuery's selectors, we could traverse the HTML we
get back and manipulate it, but it must first be inserted into the document. A
more native JavaScript data format can mean even less code.
Retrieving a JavaScript object
As we have often seen, JavaScript objects are
just sets of key-value pairs, and can be defined succinctly using curly
braces ({}). JavaScript arrays, on the other hand, are defined on the fly
with square brackets ([]). Combining these two concepts, we can easily express
some very complex and rich data structures.
The term JavaScript Object Notation (JSON)
was coined by Douglas Crockford to capitalize on this simple syntax. This
notation can offer a concise alternative to the sometimes-bulky XML format:
{ "key": "value", "key 2": [
"array", "of", "items" ] }
For information on some of the potential advantages
of JSON, as well as implementations in many programming languages, visit http://json.org/ .
We can encode our data using this format in many ways.
We'll place some dictionary entries in a JSON file we'll call b.json,
which begins as follows:
[ { "term": "BACCHUS", "part":
"n.", "definition": "A convenient deity invented by the...",
"quote": [ "Is public worship, then, a sin,", "That for
devotions paid to Bacchus", "The lictors dare to run us in,",
"And resolutely thump and whack us?" ], "author": "Jorace"
}, { "term": "BACKBITE", "part": "v.t.", "definition":
"To speak of a man as you find him when..." }, { "term":
"BEARD", "part": "n.", "definition": "The hair that is commonly
cut off by..." },
To retrieve this data, we'll use the $.getJSON()
method, which fetches the file and processes it, providing the calling code with
the resulting JavaScript object.
Global jQuery functions
To this point, all jQuery methods that we've used have
been attached to a jQuery object that we've built with the $() factory
function. The selectors have allowed us to specify a set of DOM nodes to work
with, and the methods have operated on them in some way. This $.getJSON()
function, however, is different. There is no logical DOM element to which it
could apply; the resulting object has to be provided to the script, not injected
into the page. For this reason, getJSON() is defined as a method of the
global jQuery object (a single object called jQuery or $
defined once by the jQuery library), rather than of an individual jQuery
object instance (the objects we create with the $() function).
If JavaScript had classes like other object-oriented
languages, we'd call $.getJSON() a class method. For our purposes,
we'll refer to this type of method as a global function; in effect, they
are functions that use the jQuery namespace so as not to conflict with
other function names.
To use this function, we pass it the file name as
before:
$(document).ready(function() { $('#letter-b
a').click(function() { $.getJSON('b.json'); return false;
}); });
This code has no apparent effect when we click the link.
The function call loads the file, but we have not told JavaScript what to do
with the resulting data. For this, we need to use a callback
function.
The $.getJSON() function takes a second argument,
which is a function to be called when the load is complete. As mentioned before,
AJAX calls are asynchronous, and the callback provides a way to wait for
the data to be transmitted rather than executing code right away. The callback
function also takes an argument, which is filled with the resulting data. So, we
can write:
$(document).ready(function() { $('#letter-b
a').click(function() { $.getJSON('b.json', function(data) {
}); return false; }); });
Here we are using an anonymous function as our
callback, as has been common in our jQuery code for brevity. A named function
could equally be provided as the callback.
Inside this function, we can use the data
variable to traverse the data structure as necessary. We'll need to iterate over
the top-level array, building the HTML for each item. We could do this with a
standard for loop, but instead we'll introduce another of jQuery's useful
global functions, $.each(). Instead of operating on a jQuery object, this
function takes an array or map as its first parameter and a callback function as
its second. Each time through the loop, the current iteration index and
the current item in the array or map are passed as two parameters to the
callback function.
$(document).ready(function() { $('#letter-b
a').click(function() { $.getJSON('b.json', function(data) {
$('#dictionary').empty(); $.each(data, function(entryIndex, entry)
{ var html = '<div class="entry">'; html += '<h3
class="term">' + entry['term'] + '</h3>'; html += '<div
class="part">' + entry['part'] + '</div>'; html += '<div
class="definition">'; html += entry['definition']; html
+= '</div>'; html += '</div>';
$('#dictionary').append(html); }); }); return false;
}); });
Before the loop, we empty out <div
id="dictionary"> so that we can fill it with our newly-constructed HTML.
Then we use $.each() to examine each item in turn, building an HTML
structure using the contents of the entry map. Finally, we turn this HTML into a
DOM tree by appending it to the <div>.
This approach presumes that the data is safe for HTML
consumption; it should not contain any stray < characters, for
example.
All that's left is to handle the entries with
quotations, which takes another $.each() loop:
$(document).ready(function() { $('#letter-b
a').click(function() { $.getJSON('b.json', function(data) {
$('#dictionary').empty(); $.each(data, function(entryIndex, entry)
{ var html = '<div class="entry">'; html += '<h3
class="term">' + entry['term'] + '</h3>'; html += '<div
class="part">' + entry['part'] + '</div>'; html += '<div
class="definition">'; html += entry['definition']; if
(entry['quote']) { html += '<div
class="quote">'; $.each(entry['quote'], function(lineIndex,
line) { html += '<div class="quote-line">' + line +
'</div>'; }); if (entry['author'])
{ html += '<div class="quote-author">' + entry['author']
+ '</div>'; } html +=
'</div>'; } html += '</div>';
html += '</div>'; $('#dictionary').append(html);
}); }); return false; }); });
With this code in place, we can click the B link
and confirm our results:
The JSON format is concise, but not forgiving. Every
bracket, brace, quote, and comma must be present and accounted for, or the file
will not load. In most browsers, we won't even get an error message; the script
will just silently fail.
Executing a script
Occasionally we don't want to retrieve all the
JavaScript we will need when the page is first loaded. We might not know what
scripts will be necessary until some user interaction occurs. We could introduce
<script> tags on the fly when they are needed, but a more elegant
way to inject additional code is to have jQuery load the .js file
directly.
Pulling in a script is about as simple as loading an
HTML fragment. In this case, we use the global function $.getScript(),
which, like its siblings, accepts a URL locating the script file:
$(document).ready(function() { $('#letter-c
a').click(function() { $.getScript('c.js'); return false;
}); });
Scripts fetched in this way are run in the global
context of the current page. This means they have access to all
globally-defined functions and variables, notably including jQuery itself. We
can therefore mimic the JSON example to prepare and insert HTML on the page when
the script is executed, and place this code in c.js:.
var entries = [ { "term": "CALAMITY",
"part": "n.", "definition": "A more than commonly plain and..."
}, { "term": "CANNIBAL", "part": "n.", "definition":
"A gastronome of the old school who..." }, { "term":
"CHILDHOOD", "part": "n.", "definition": "The period of human life
intermediate..." }, { "term": "CLARIONET", "part":
"n.", "definition": "An instrument of torture operated by..."
}, { "term": "COMFORT", "part": "n.", "definition": "A
state of mind produced by..." }, { "term": "CORSAIR",
"part": "n.", "definition": "A politician of the seas."
} ];
var html = '';
$.each(entries, function() { html +=
'<div class="entry">'; html += '<h3 class="term">' +
this['term'] + '</h3>'; html += '<div class="part">' +
this['part'] + '</div>'; html += '<div class="definition">' +
this['definition'] + '</div>'; html +=
'</div>'; });
$('#dictionary').html(html);
Now clicking on the C link has the expected
result:
Loading an XML document
XML is part of the acronym AJAX, but we haven't actually
loaded any XML yet. Doing so is straightforward, and mirrors the JSON technique
fairly closely. First we'll need an XML file d.xml containing some data
we wish to display, excerpted here:
<?xml version="1.0"
encoding="UTF-8"?> <entries> <entry term="DEFAME"
part="v.t."> <definition> To lie about another. To
tell the truth about another. </definition>
</entry> <entry term="DEFENCELESS" part="adj.">
<definition> Unable to attack. </definition>
</entry> <entry term="DELUSION" part="n.">
<definition> The father of a most respectable family,
comprising Enthusiasm, Affection, Self-denial, Faith, Hope,
Charity and many other goodly sons and daughters.
</definition> <quote author="Mumfrey Mappel">
<line>All hail, Delusion! Were it not for thee</line>
<line>The world turned topsy-turvy we should see;
</line> <line>For Vice, respectable with cleanly
fancies, </line> <line>Would fly abandoned
Virtue's gross advances. </line> </quote>
</entry> <entry term="DIE" part="n.">
<definition> The singular of "dice." We seldom hear the
word, because there is a prohibitory proverb, "Never say
die." At long intervals, however, some one says: "The die is cast,"
which is not true, for it is cut. The word is found in an immortal
couplet by that eminent poet and domestic economist, Senator
Depew: </definition> <quote> <line>A
cube of cheese no larger than a die</line> <line>May bait
the trap to catch a nibbling mie.</line> </quote>
</entry> </entries>
This data could be expressed in many ways, of course,
and some would more closely mimic the structure we established for the HTML or
JSON used earlier. Here, however, we're illustrating some of the features of XML
designed to make it more readable to humans, such as the use of
attributes for term and part rather than tags.
$(document).ready(function() { $('#letter-d
a').click(function() { $.get('d.xml', function(data) {
}); return false; }); });
This time it's the $.get() function that does our
work. In general, this function simply fetches the file at the supplied URL and
provides the plain text to the callback. However, if the response is known to be
XML because of its server-supplied MIME type, the callback will be handed
the XML DOM tree.
Fortunately, as we have already seen, jQuery has
substantial DOM traversing capabilities. We can use the normal .find(),
.filter() and other traversal methods on the XML document just as we would
on HTML:
$(document).ready(function() { $('#letter-d
a').click(function() { $.get('d.xml', function(data) {
$('#dictionary').empty(); $(data).find('entry').each(function()
{ var $entry = $(this); var html = '<div
class="entry">'; html += '<h3 class="term">' +
$entry.attr('term') + '</h3>'; html += '<div
class="part">' + $entry.attr('part') +
'</div>'; html += '<div class="definition">';
html += $entry.find('definition').text(); var $quote =
$entry.find('quote'); if ($quote.length) { html +=
'<div class="quote">'; $quote.find('line').each(function()
{ html += '<div class="quote-line">' +
$(this).text() + '</div>'; }); if
($quote.attr('author')) { html += '<div
class="quote-author">' + $quote.attr('author') +
'</div>'; } html += '</div>';
} html += '</div>'; html +=
'</div>'; $('#dictionary').append($(html)); });
}); return false; }); });
This has the expected effect when the D link is
clicked:
This is a new use for the DOM traversal methods we
already know, shedding some light on the flexibility of jQuery's CSS selector
support. CSS syntax is typically used to help beautify HTML pages, and thus
selectors in standard .css files use HTML tag names such as div
and body to locate content. However, jQuery can use arbitrary XML tag
names, such as entry and definition here, just as readily as the
standard HTML ones.
The advanced selector engine inside jQuery facilitates
finding parts of the XML document in much more complicated situations, as well.
For example, suppose we wanted to limit the displayed entries to those that have
quotes that in turn have attributed authors. To do this, we can limit the
entries to those with nested <quote> elements by changing
entry to entry:has(quote). Then we can further restrict the
entries to those with author attributes on the <quote> elements by
writing entry:has(quote[author]). The line with the initial selector now
reads:
$(data).find('entry:has(quote[author])').each(function()
{
This new selector expression restricts the returned
entries correspondingly:
|