Ajax in a Nutshell
Accessing server-based web applications from browsers originally involved inputting content via an HTML form's fields, clicking a submit button to send this content to the web app, and receiving (from the web app) a new page whose content replaced the form page.This experience proved to be less elegant than that of interacting with desktop programs, which "stay on the same page." It also felt sluggish due to server-processing delays, network latency, and other delays between form submission and new page response. Ajax was created to improve this experience.
What is Ajax?
Ajax, an acronym for Asynchronous JavaScript and XML, is a group of interrelated web technologies (JavaScript, XMLHttpRequest, XML, DOM, HTML, CSS) used on the browser side to create interactive web apps. Browsers use Ajax to send data asynchronously (or even synchronously) to servers so that web pages don't need to be reloaded.Data is sent via JavaScript and XMLHttpRequest; retrieved via these technologies, XML, and the DOM; and marked up and styled via HTML and CSS. Returned data 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 DOM-based data object.
What is XMLHttpRequest?
XMLHttpRequest is a 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 app and retrieve application response data for
processing and presentation.
The following steps are required to work with the
XMLHttpRequest object:
-
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 the DOMwindowobject. 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 in this article. -
Prepare to send an HTTP/HTTPS request to a web app 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 app via a URL. Three additional (and 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 when this rule is 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 app by specifying it as this function's solitary argument, but it's common to passnullto indicate 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 the
readyState property is 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.
When the returned data is a valid XML document,
XMLHttpRequest's responseXML property
contains a DOM object that provides access to this
content via its methods. Also, the responseText
property usually contains the response in plain text, regardless
of the content being sent back as an XML document.
The following JavaScript code reveals the general architecture for
detecting XMLHttpRequest and using this object to
send an asynchronous request to a web app:
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 = ... // web application's URL
xmlHttpReq.onreadystatechange = stateChanged;
xmlHttpReq.open("GET", url);
xmlHttpReq.send(null);
function stateChanged()
{
if (xmlHttpReq.readyState == 4)
{
// Process the returned data.
}
}
You'll encounter this architecture in the following Ajax examples, which count vowels and consonants, fetch a calendar for a specific year and month, and fetch zip code-specific weather information.
Vowels and Consonants
Our first Ajax example shows you how to use Ajax to count vowels and consonants as characters are entered into a textfield. For each entered character, a PHP script is invoked to compute these metrics, which are then displayed below the textfield. Figure 1 reveals this example in action.
Figure 1: Vowels and consonants are counted and these counts are output as text is entered.
This example relies on Listing 1, which includes a JavaScript
source file that defines a function named countVaC(),
and which 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 this example
As keys are pressed, this form's textfield's onkeyup
event handler invokes countVaC(this.value) to pass
the textfield'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://tutortutor.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. 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 our second Ajax example, you select a month and year from a form's dropdown months and years listboxes. For each selection, a PHP script is invoked to create a calendar for that month/year, and the resulting calendar is displayed below these listboxes. Figure 2 reveals a sample calendar.
Figure 2: A calendar is displayed whenever you select a month or a year.
This example relies on Listing 4, which includes a JavaScript
source file that defines a function named
obtainCal(), and which 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 this example
As listbox selections are made, the form's selected listbox's
onchange event handler invokes
obtainCal(months.value, years.value) to pass the
listbox selections to this function, shown 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://tutortutor.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 our final Ajax example, you enter a United States zip code into a textfield 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. Figure 3 reveals weather information for a famous zip code.
Figure 3: The latest weather details are presented for the entered zip code.
This example relies on Listing 7, which includes a JavaScript
source file that defines a function named wi(), and
which specifies an HTML form that invokes this function.
<script type="text/javascript" src="wi.js">
</script>
<form action="">
<p>
<label>
Enter zip code:
</label>
<input name="zip" type = "text">
<input type="button" onclick="wi(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 this example
Each time this form's button is pressed, its onclick
event handler invokes wi(zip.value) to pass the
textfield's contents to this function, which Listing 8 reveals.
function wi(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://tutortutor.ca/cgi-bin/wi.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];
if (units == null)
units = xmlDoc.getElementsByTagNameNS("*", "units")[0];
if (units == null)
{
alert("Your browser doesn't properly handle the DOM API's "+
"getElementsByTagName() function. Please use a more "+
"capable browser (such as Mozilla Firefox 3.5.13) to "+
"run this example.");
return;
}
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];
if (cond == null)
cond = xmlDoc.getElementsByTagNameNS("*", "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: wi.js
After obtaining the XMLHttpRequest object,
wi() 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: wi.php
When send() returns, wi() 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
wi.php instead of wi.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 provides a
workaround that lets |
Exercises
-
What is Ajax?
-
What is XMLHttpRequest?
-
For Firefox, Chrome, Opera, Safari, and IE7+, the
XMLHttpRequestobject is a property of which DOM object? -
Identify the parameters of the
XMLHttpRequestobject'sopen()function. Which parameters are required and which parameters are optional? -
Why do I call
getElementsByTagNameNS()in addition togetElementsByTagName()? -
Suppose you have a
date.phpscript that outputs the current date. Write agetDate()function that's similar to the earliercountVaC(str)function, but which takes no arguments, uses Ajax to runhttp://somesite/cgi-bin/date.php, and stores the resulting date text in a span whoseidattribute's value is set todate.
Summary
Ajax is a group of interrelated web technologies (JavaScript, XMLHttpRequest, XML, DOM, HTML, CSS) used on the browser side to create interactive web apps. Browsers use Ajax to send data asynchronously (or even synchronously) to servers so that web pages don't need to be reloaded.
|
Development Tools
Windows Notepad Testing Platforms Mozilla Firefox 3.6.13 on Windows XP SP3 and my ISP's Linux web server with PHP 5.2.13 |





