Ajax By Example
Historically, accessing Web applications from Web browsers consisted of inputting content via an HTML form's fields, clicking a submit button to send this content to the server, and receiving (from the server) a new page whose content replaced the form page.This experience proved to be less elegant than the experience of interacting with desktop programs, which "stay on the same page." Furthermore, the experience felt sluggish due to Web server processing delays, network latency, and other delays between submitting a page and receiving a response. This problem needed a solution.
Ajax, an acronym for Asynchronous JavaScript and XML, was developed as this problem's solution. It uses JavaScript, XML, CSS, XMLHttpRequest, and other standard Web technologies to improve the interaction experience with Web applications.
This article introduces you to Ajax in an example-oriented fashion. Before presenting examples that demonstrate this technology suite, the article explores XMLHttpRequest, which improves browser-based script interaction with server-based Web applications by letting scripts communicate asynchronously with these applications.
XMLHttpRequest
XMLHttpRequest is a Document Object Model (DOM) API that's accessed from a scripting language (JavaScript, VBScript, and so on), via a typically same-namedXMLHttpRequest object,
to send HTTP/HTTPS requests to a Web application and retrieve
application response data for processing and presentation.
The data that is returned from the Web application typically consists of an XML document or plain text. If plain text is returned, it may be formatted as JavaScript Object Notation (JSON), and subsequently evaluated within JavaScript to create a data object for use with the current DOM.
The following steps are required to work with
XMLHttpRequest:
-
Detect the presence of the
XMLHttpRequestobject. This can be accomplished by invoking the following JavaScript code:var xmlHttpReq; if (window.XMLHttpRequest) { // The following is supported by Firefox, Chrome, Opera, Safari, and IE7+. xmlHttpReq = new XMLHttpRequest (); } else if (window.ActiveXObject) { // The following is supported by IE5 and IE6. xmlHttpReq = new ActiveXObject ("Microsoft.XMLHTTP"); } else { alert ("Your browser doesn't support XMLHttpRequest!"); }A browser that supports
XMLHttpRequestwill indicate this support by providingXMLHttpRequestas a property of thewindowobject. BecauseXMLHttpRequestwasn't standardized when IE5 and IE6 were released, this object must be obtained via ActiveX on these browsers. -
Assign a function to
XMLHttpRequest'sonreadystatechangeproperty. This function is invoked (only for asynchronous requests) wheneverXMLHttpRequest'sreadyStateproperty changes. I'll discuss this topic later. -
Prepare to send an HTTP/HTTPS request to a Web application by
invoking
XMLHttpRequest'sopen()function. The first argument passed to this function is a string that identifies the HTTP request method, typically GET or POST. The second argument is a string identifying the Web application via a URL. Three additional optional arguments (a Boolean flag indicating a synchronous request rather than the default asynchronous request, a user ID, and a password) may be passed (as necessary).For security reasons, the URL must not specify protocol, host, and/or port components that differ from the equivalent components in the invoking document's URL. The browser is expected to raise a security error should this rule be violated. This requirement may be relaxed in a future version of the XMLHttpRequest API.
-
Invoke
XMLHttpRequest'ssetRequestHeader()function to initialize any required HTTP headers before making the request. The first argument passed to this function is a string containing the header name. The second argument is a string containing the header value. Each invocation initializes a single header. Also, these function calls must be made after eachopen()call -- they aren't cached. -
Invoke
XMLHttpRequest'ssend()function to send the request. Content may be sent to the Web application by specifying it as this function's solitary argument, but it's common to passnullindicating that content is being sent via an HTTP request method.For asynchronous requests (the default), this function returns immediately. The script obtains returned data from within the function assigned to
onreadystatechange. For synchronous requests (truewas passed toopen()as the third argument, for browsers that support this kind of request),send()blocks until the request finishes.
During an asynchronous request, the function assigned to
onreadystatechange is invoked whenever an activity
causes the readyState property to be changed:
-
Following a successful call to
open(),1is assigned toreadyState. -
Following a call to
send()and the retrieval of HTTP response headers,2is assigned toreadyState. -
Once HTTP request content starts loading,
3is assigned toreadyState. -
After HTTP request content finishes loading,
4is assigned toreadyState.
Code within the function assigned to
onreadystatechange typically checks
readyState to see if it equals 4. In
this case, the code can proceed to obtain and process the returned
content. Similarly, after a synchronous send() call
completes, code following this call can obtain and process the
returned content.
If the Web application returns a valid XML document,
XMLHttpRequest's responseXML property
will contain a DOM document object that provides access to this
content via various methods. Also, the responseText
property will typically contain the response in plain text,
regardless of the content being sent back as an XML document.
Vowels and Consonants
In the first example, you enter characters into a form's text field. For each entered character, a PHP script is invoked to count the number of vowels and consonants in the text that has so far been entered; the resulting metrics are displayed below the form.Vowels and consonants:
This example relies on Listing 1, which includes a JavaScript
source file that defines a function named countVaC(),
and specifies an HTML form that invokes this function.
<script type="text/javascript" src="vac.js">
</script>
<form action="">
<p>
<label>
Enter some text:
</label>
<input type = "text" onkeyup = "countVaC(this.value)">
</form>
<p>Vowels and consonants: <span id = "vac"></span></p>
Listing 1: Client-side HTML for the first example
As keys are pressed, this form's text field's onkeyup
event handler invokes countVaC(this.value) to pass
the text field's contents to this function, which Listing 2
reveals.
function countVaC (str)
{
var xmlHttpReq;
if (window.XMLHttpRequest)
{
// The following is supported by Firefox, Chrome, Opera, Safari, and IE7+.
xmlHttpReq = new XMLHttpRequest ();
}
else
if (window.ActiveXObject)
{
// The following is supported by IE5 and IE6.
xmlHttpReq = new ActiveXObject ("Microsoft.XMLHTTP");
}
else
{
alert ("Your browser doesn't support XMLHttpRequest!");
return;
}
var url = "http://www.javajeff.mb.ca/cgi-bin/vac.php";
url = url+"?txt="+str;
xmlHttpReq.onreadystatechange = stateChanged;
xmlHttpReq.open ("GET", url);
xmlHttpReq.send (null);
function stateChanged ()
{
if (xmlHttpReq.readyState == 4)
{
document.getElementById ("vac").innerHTML = xmlHttpReq.responseText;
}
}
}
Listing 2: vac.js
After obtaining the XMLHttpRequest object,
countVaC() builds a URL that identifies the PHP
script (see Listing 3) to execute along with its str
argument. It then assigns a function that processes the script's
returned text to onreadystatechange, invokes
open() to identify the GET request method and the
script's URL, and invokes send() to launch the
script.
<?php
$nvowels = 0;
$nconsonants = 0;
// Get the txt parameter from the URL.
$txt = $_GET ["txt"];
// Scan txt for vowels and consonants.
for ($i = 0; $i < strlen ($txt); $i++)
{
$ch = substr ($txt, $i, 1);
if ($ch == 'a' || $ch == 'A' || $ch == 'e' || $ch == 'E' ||
$ch == 'i' || $ch == 'I' || $ch == 'o' || $ch == 'O' ||
$ch == 'u' || $ch == 'U' || $ch == 'y' || $ch == 'Y')
$nvowels++;
else
if (($ch >= 'B' && $ch <= 'Z') || ($ch >= 'b' && $ch <= 'z'))
$nconsonants++;
}
echo "Vowels = $nvowels; Consonants = $nconsonants";
?>
Listing 3: vac.php
When send() returns, countVaC() exits --
it's reinvoked when a key is pressed. Meanwhile,
stateChanged() is asynchronously invoked as described
earlier. When this function detects a readyState
value of 4, it embeds the script's response text in
Listing 1's vac span (an HTML element for
adding inline structure to a document).
The response text is embedded in this span via
document.getElementById ("vac").innerHTML =
xmlHttpReq.responseText;. After
document.getElementById ("vac") uses the DOM to
obtain the HTML element object whose CSS ID is vac,
the text is embedded into this element by assigning
xmlHttpReq.responseText to the object's
innerHTML property.
Calendar
In the second example, you select a month from a form's months dropdown listbox, or a year from the years dropdown listbox. For each selection, a PHP script is invoked to create a calendar for that month/year combination, and the resulting calendar is displayed below these listboxes.Calendar:
This example relies on Listing 4, which includes a JavaScript
source file that defines a function named
obtainCal(), and specifies an HTML form that invokes
this function.
<script type="text/javascript" src="cal.js">
</script>
<form action="">
<p>
<label>
Month
</label>
<select name="months" onchange="obtainCal(months.value, years.value)">
<option value="0">---</option>
<option value="1">January</option>
<option value="2">February</option>
<option value="3">March</option>
<option value="4">April</option>
<option value="5">May</option>
<option value="6">June</option>
<option value="7">July</option>
<option value="8">August</option>
<option value="9">September</option>
<option value="10">October</option>
<option value="11">November</option>
<option value="12">December</option>
</select>
<label>
Year
</label>
<select name="years" onchange="obtainCal(months.value, years.value)">
<option value="0">---</option>
<option value="2000">2000</option>
<option value="2001">2001</option>
<option value="2002">2002</option>
<option value="2003">2003</option>
<option value="2004">2004</option>
<option value="2005">2005</option>
<option value="2006">2006</option>
<option value="2007">2007</option>
<option value="2008">2008</option>
<option value="2009">2009</option>
<option value="2010">2010</option>
<option value="2011">2011</option>
</select>
</form>
<p>Calendar:</p>
<div id="cal">
</div>
Listing 4: Client-side HTML for the second example
As listbox selections are made, the form's selected listbox's
onchange event handler invokes
obtainCal(months.value, years.value) to pass both
listbox selections to this function, which is presented in Listing
5.
function obtainCal (month, year)
{
var xmlHttpReq;
if (window.XMLHttpRequest)
{
// The following is supported by Firefox, Chrome, Opera, Safari, and IE7+.
xmlHttpReq = new XMLHttpRequest ();
}
else
if (window.ActiveXObject)
{
// The following is supported by IE5 and IE6.
xmlHttpReq = new ActiveXObject ("Microsoft.XMLHTTP");
}
else
{
alert ("Your browser doesn't support XMLHttpRequest!");
return;
}
var url = "http://www.javajeff.mb.ca/cgi-bin/cal.php";
url = url+"?month="+month+"&year="+year;
xmlHttpReq.onreadystatechange = stateChanged;
xmlHttpReq.open ("GET", url);
xmlHttpReq.send (null);
function stateChanged ()
{
if (xmlHttpReq.readyState == 4)
{
document.getElementById ("cal").innerHTML = xmlHttpReq.responseText;
}
}
}
Listing 5: cal.js
After obtaining the XMLHttpRequest object,
obtainCal() builds a URL that identifies the PHP
script (see Listing 6) to execute along with its
month and year arguments. It then
assigns a function that processes the script's returned text to
onreadystatechange, invokes open() to
identify the GET request method and the script's URL, and invokes
send() to launch the script.
<?php
// Get the month parameter from the URL. Abort if 0 passed.
$month = (int) $_GET ["month"];
if ($month == 0)
return;
$month--;
// Get the year parameter from the URL. Abort if 0 passed.
$year = (int) $_GET ["year"];
if ($year == 0)
return;
// Create the calendar.
$daysInMonth = array (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
if (isleap ($year))
$daysInMonth [1]++;
$maxDays = $daysInMonth [$month];
$months = array ("January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November",
"December");
echo "<table border=1>";
echo "<th bgcolor=#eeaa00 colspan=7>";
echo "<center>".$months [$month]." $year</center>";
echo "</th>";
echo "<tr bgcolor=#ff7700>";
echo "<td><b><center>S</center></b></td>";
echo "<td><b><center>M</center></b></td>";
echo "<td><b><center>T</center></b></td>";
echo "<td><b><center>W</center></b></td>";
echo "<td><b><center>T</center></b></td>";
echo "<td><b><center>F</center></b></td>";
echo "<td><b><center>S</center></b></td>";
echo "</tr>";
$dayOfWeek = date ("w", mktime (12, 0, 0, $month+1, 1, $year));
$day = 1;
for ($row = 0; $row < 6; $row++)
{
echo "<tr>";
for ($col = 0; $col < 7; $col++)
{
if (($row == 0 && $col < $dayOfWeek) || $day > $maxDays)
{
echo "<td bgcolor=#cc6622>";
echo " ";
}
else
{
if ($day%2 == 0)
echo "<td bgcolor=#ff9940>";
else
echo "<td>";
echo $day++;
}
echo "</td>";
}
echo "</tr>";
}
echo "</table>";
function isleap ($year)
{
return (!($year & 3) && $year%100 || !($year%400));
}
?>
Listing 6: cal.php
When send() returns, obtainCal() exits
-- it's reinvoked when a selection is made. Meanwhile,
stateChanged() is asynchronously invoked as described
earlier. When this function detects a readyState
value of 4, it embeds the script's response text in
Listing 4's cal div (an HTML element for
adding block structure to a document).
The response text is embedded in this div via
document.getElementById ("cal").innerHTML =
xmlHttpReq.responseText;. After
document.getElementById ("cal") uses the DOM to
obtain the HTML element object whose CSS ID is cal,
the text is embedded into this element by assigning
xmlHttpReq.responseText to the object's
innerHTML property.
Weather Information
In the third example, you enter a United States zip code into a text field and click a button. In response, a PHP script is invoked to obtain the most recent weather information for that zip code (as an XML document) from Yahoo! Weather. The document is parsed and weather information is presented.Weather information:
This example relies on Listing 7, which includes a JavaScript
source file that defines a function named gwi(), and
specifies an HTML form that invokes this function.
<script type="text/javascript" src="gwi.js">
</script>
<form action="">
<p>
<label>
Enter zip code:
</label>
<input name="zip" type = "text">
<input type="button" onclick="gwi(zip.value)" value="Get Weather Information">
</form>
<p>Weather information:</p>
<div>
<div id="wi" style="float: left">
</div>
<div style="clear: both">
</div>
</div>
Listing 7: Client-side HTML for the third example
Each time this form's button is pressed, its onclick
event handler invokes gwi(zip.value) to pass the text
field's contents to this function, which Listing 8 reveals.
function gwi (zip)
{
var xmlHttpReq;
if (window.XMLHttpRequest)
{
// The following is supported by Firefox, Chrome, Opera, Safari, and IE7+.
xmlHttpReq = new XMLHttpRequest ();
}
else
if (window.ActiveXObject)
{
// The following is supported by IE5 and IE6.
xmlHttpReq = new ActiveXObject ("Microsoft.XMLHTTP");
}
else
{
alert ("Your browser doesn't support XMLHttpRequest!");
return;
}
var url = "http://www.javajeff.mb.ca/cgi-bin/gwi.php";
url = url+"?zip="+zip;
xmlHttpReq.onreadystatechange = stateChanged;
xmlHttpReq.open ("GET", url);
xmlHttpReq.send (null);
function stateChanged ()
{
if (xmlHttpReq.readyState == 4)
{
var result = "<div style='border: solid; padding: 10px'>";
result += "<center><strong>";
var xmlDoc = xmlHttpReq.responseXML;
var desc = xmlDoc.getElementsByTagName ("description") [0].
childNodes [0].nodeValue;
result += desc + "<br>";
// If valid ZIP was entered, desc.indexOf() doesn't return -1.
if (desc.indexOf ("Error") == -1)
{
// Extract units indicator (C, Celsius, or F, Fahrenheit)
var units = xmlDoc.getElementsByTagName ("yweather:units") [0];
var attr = units.attributes;
// Extract temperature.
var temperature = "";
for (var i = 0; i < attr.length; i++)
if (attr [i].nodeName == "temperature")
temperature = attr [i].nodeValue;
// Extract weather conditions.
var cond = xmlDoc.getElementsByTagName ("yweather:condition") [0];
attr = cond.attributes;
var text = ""; // Sunny, cloudy, etc.
var code = ""; // Conditions image GIF file ID
var temp = ""; // Temperature
var date = ""; // Date to which conditions apply
for (var i = 0; i < attr.length; i++)
if (attr [i].nodeName == "text")
text = attr [i].nodeValue;
else
if (attr [i].nodeName == "code")
code = attr [i].nodeValue;
else
if (attr [i].nodeName == "temp")
temp = attr [i].nodeValue;
else
if (attr [i].nodeName == "date")
date = attr [i].nodeValue;
result += "As of: " + date + "<br><br>";
// Extract conditions image GIF and border it with a table.
result += "<table border=1 bgcolor=#ffffff><tr><td>";
result += "<img src='http://l.yimg.com/us.yimg.com/i/us/we/52/"+
code+".gif'";
result += "</td></tr></table><br>";
// Append current conditions to result.
result += "Current Conditions: ";
result += text + ", " + temp + " " + temperature;
}
result += "</strong></center></div>";
document.getElementById ("wi").innerHTML = result;
}
}
}
Listing 8: gwi.js
After obtaining the XMLHttpRequest object,
gwi() builds a URL that identifies the PHP script
(see Listing 9) to execute along with its zip
argument. It then assigns a function that processes the script's
returned XML content to onreadystatechange, invokes
open() to identify the GET request method and the
script's URL, and invokes send() to launch the
script.
<?php
// Get the zip argument from the PHP URL.
$zip = $_GET ["zip"];
// Create the Yahoo weather URL for the specified zip.
$url = "http://weather.yahooapis.com/forecastrss?p=".$zip;
// We're sending XML content back to the browser.
header ('Content-Type: text/xml');
// Echo the weather XML associated with the specified zip to the browser.
echo file_get_contents ($url);
?>
Listing 9: gwi.php
When send() returns, gwi() exits -- it's
reinvoked when the button is pressed. Meanwhile,
stateChanged() is asynchronously invoked as described
earlier. When this function detects a readyState
value of 4, it obtains the returned XML document via
responseXML, and uses the DOM API to extract
meaningful values from this object.
The parsed content is embedded in the div identified as
wi (see Listing 7) via document.getElementById
("wi").innerHTML = result;. This div's float:
left style causes the div to appear on the left side of the
page with its content remaining centered, instead of stretching
the div with its centered content across the page.
If you're wondering why I access Yahoo! Weather via
gwi.php instead of gwi.js, recall that
XMLHttpRequest requires the target URL to specify the same
protocol, host, and port components as the invoking document's
URL. This is known as the same origin
policy.
The Mozilla team behind Firefox has provided a
workaround that allows XMLHttpRequest to perform
cross-site HTTP requests. This solution was first
introduced into Firefox 3.5, and makes use of an
Origin HTTP request header and an
Access-Control-Allow-Origin response header.
Conclusion
If you've never worked with Ajax, you should now possess a basic understanding of how to use this technology suite, and (after playing with this article's examples) how Ajax can make a website more dynamic. From time to time, I'll introduce additional examples that demonstrate more of what Ajax has to offer.
Note: All code created and tested with Mozilla Firefox 3.5.4, Internet Explorer 8.0.6001.18702, and PHP 4.4.8









