How to Bridge the Client-Server Gap using AJAX (Part I)

0
147
17 min read

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.

Let us see if AJAX can truly perform miracles.

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:

How to Bridge the Client-Server Gap using AJAX (Part I)

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 &mdash;</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:

How to Bridge the Client-Server Gap using AJAX (Part I)

 

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:

How to Bridge the Client-Server Gap using AJAX (Part I)

 

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:

How to Bridge the Client-Server Gap using AJAX (Part I)

 

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:

How to Bridge the Client-Server Gap using AJAX (Part I)

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:

How to Bridge the Client-Server Gap using AJAX (Part I)

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

About the author

)
. The line with the initial selector now reads:

$(data).find('entry:has(quote
		

About the author

)').each(function() {

This new selector expression restricts the returned entries correspondingly:

How to Bridge the Client-Server Gap using AJAX (Part I)

 

 

LEAVE A REPLY

Please enter your comment!
Please enter your name here