272 Chapter 14
<html><head><title>Demonstrating Ansynchronicity</title>
<script type = "text/javascript">
<! hide me from older browsers
X function demoAsync() {
var now = new Date();
downloadFile("longWait", now );
downloadFile("shortWait", now);
}
Y function downloadFile(the_request, start_time) {
var request = null;
if (window.XMLHttpRequest) {
request = new XMLHttpRequest();
} else if (window.ActiveXObject) {
request = new ActiveXObject("Microsoft.XMLHTTP");
}
if (request) {
request.open("GET", "http://localhost/boj/" + the_request +
".php");
Z request.onreadystatechange =
function() {
if (request.readyState == 4) {
[ document.getElementById("resultDiv").innerHTML +=
"File " + the_request + " was downloaded in " +
\ getExpiredTime(start_time) + " seconds <br>";
}
}
request.send(null);
} else {
alert("Sorry, you must update your browser before seeing" +
" Ajax in action.");
tion. It begins by trying to create a new request object, and if that succeeds, it
tells the request which resource to access.
The anonymous function that is called when the request changes its state
is defined in Z. This function says that we should add some information to
the contents of the
div named resultDiv ([) once the request has completed
(that is, when the request object’s
readyState property equals 4). The informa-
tion added is the name of the requested file and the time it took to download.
Line \ calls
getExpiredTime() to determine how long (in seconds) it took to
download the file.
SETTING UP A WEBSERVER AND PHP
Below you’ll find some resources for setting up the Apache webserver and PHP on
your desktop machine. Apache has been the most popular webserver on the Internet
for more than 10 years. It is very robust, has loads of features, and works on all
modern Windows, Macintosh, and Unix (including the major flavors of Linux) operat-
ing systems. In the spirit of full disclosure, I should mention that my cousin, Robert S.
Thau, was one of the original authors of Apache. Hooray, Robert!
For Windows and Linux
If you are using Windows 98, NT, 2000, or XP, or a major version of Linux
(Debian, Mandrake, Red Hat, or SuSE), you can install Apache and PHP
using one easy package called XAMPP from Apache Friends. The package
is available at http://www.apachefriends.org/en/xampp.html and at http://
www.bookofjavascript.com/Freeware/xampp. The useful MySQL database
and a few other things are also included. If you want to get up and running
quickly, I suggest you use XAMPP.
For Macintosh OS X Server
If you’re running the Macintosh OS X Server operating system (http://www
.apple.com/server/macosx)—which is different from its desktop operating system—
button does not work as expected in Ajax applications. For example, if you
click the left side of a Google map and drag it to the right side of the screen,
the map will change, but clicking the browser’s back button won’t return the
map to its previous state. Instead, because all of an Ajax application happens
on a single web page, clicking back will take you off that web page. In the case
of Google Maps, this may take you out of Google Maps entirely. You can use
many of the Ajax frameworks described in Appendix B to help make the
browser’s back button work in ways that will make more sense to your visitors.
Dojo (http://www.dojotoolkit.org), Backbase (http://www.backbase.com), and
RSH (http://codinginparadise.org/projects/dhtml_history/README.html)
are three examples of such libraries.
URLs and Bookmarking
Web page URLs can be written down, sent to friends, and bookmarked.
However, because the URL of a web page for an Ajax application does not
change as the contents of the page change (all updates happen on the same
page), special care must be taken to create URLs that can be bookmarked
and emailed. Again, you’ll find solutions to this problem in the Ajax frame-
works in Appendix B.
Poor Design
People who have been browsing web pages for any length of time are
probably all too familiar with the usual submit-wait-reload method of web
interaction. In this style of communication, the entire web page updates
Ajax Basics 275
when new information is returned from the server, which also signals to the
visitor that the whole page is new. When using Ajax, on the other hand, the
contents of a web page might change without the visitor noticing any signs
of a change. As a web designer, you should be sure to signify important
changes to web pages using design techniques, such as changing color
or borders.
Ajax also offers new types of navigation to the web designer. Pre-Ajax,
most likely result in fewer users.
3
See http://www.usabilityviews.com/ajaxsucks.html. Note that this article is a rewriting of an older
article by Jakob Neilsen titled “Why Frames Suck (Most of the Time),” which is available at
http://www.useit.com/alertbox/9612.html.
4
See http://sourcelabs.com/ajb/archives/2005/05/ajax_mistakes.html.
276 Chapter 14
Good: In-Context Data Manipulation
Imagine you are the CEO of a big company, and you are presented with a
table of information about your employees. This table may include the name,
salary, and tenure of each employee. Now, imagine you want to sort that table
by name, salary, or tenure. Without Ajax, you would click a button and wait
for the whole page to reload. With Ajax, the table can stay on the screen
while the data are being rearranged, which is far less disruptive.
In this example, the object you are manipulating stays in front of you
while you are manipulating it. This is a good use of Ajax.
Another example of in-context data manipulation is Google Maps’ use
of Ajax. In Google Maps, you can move the map by dragging on it. The rest of
the page does not change, and at no point is the screen blank. Mapping appli-
cations that do not use Ajax reload the entire web page each time you want
to pan a map.
Good: Interactive Widgets
Interactive widgets are small components of a website that generally appear
on the margins of a web page. Items such as news tickers, quick one-question
polls (“Do you prefer cats or dogs?”), and login forms fall into this category.
In each of these cases, interacting with the widget may produce results that
do not require the entire web page to reload.
For example, if a visitor tries to log in with an invalid username or
password, the error message might as well appear on the current web page,
div. Figure 14-7 depicts how a solution might look after a user
entered sample.txt into the text field and clicked the Get the File button.
Remember, the file that Ajax reads must live in the same directory as one
that contains the Ajax code.
Figure 14-7: Displaying the contents of a file
XML IN JAVASCRIPT AND AJAX
Chapter 14 introduced Ajax and showed
how it works inside web browsers. Normally,
Ajax applications pass information back and
forth between a web browser and a webserver.
When a user drags a Google map, for example, the
browser sends information to the server about how the user is dragging
the map. The server then returns map-related information for the browser
to interpret and display.
Information passed back and forth between a web browser and a web-
server can take many forms. This chapter discusses the form used by Ajax: XML.
In this chapter you will learn:
z What XML is and why it’s useful
z How to format information using XML
z How to use JavaScript to read XML documents
z What browser-compatibility issues relate to processing XML
z How to use XML in Ajax communications
280 Chapter 15
Once you’ve mastered the intricacies of XML, you’ll be ready for
Chapter 16, the final Ajax chapter. There you will learn how to write the
server-side code for Ajax communications.
A Real-World Example of Ajax and XML
The photo-sharing community site Flickr (http://www.flickr.com) provides
many fancy web-based tools for uploading, editing, and sharing your photos.
<?xml version = "1.0" encoding = "utf-8" ?>
<rsp stat = "ok">
<photos page = "1" pages = "1" perpage = "100" total = "32">
<photo id = "51544990" owner = "76267260@N00" secret = "f69c737a26" server
= "27" title = "Strawberry Picking down the 1" ispublic = "1" isfriend = "0"
isfamily = "0" dateupload = "1129035992" datetaken = "2005-09-26 00:14:57"
datetakengranularity = "0" latitude = "0" longitude = "0" accuracy = "0"
ownername = "thau" iconserver = "0" />
<photo id = "51544989" owner = "76267260@N00" secret = "e64704958b" server
= "33" title = "Fuel Cell Bus at Davis" ispublic = "1" isfriend = "0" isfamily
= "0" dateupload = "1129035992" datetaken = "2005-10-01 17:12:52"
datetakengranularity = "0" latitude = "0" longitude = "0" accuracy = "0"
ownername = "thau" iconserver = "0" />
<photo id = "51542442" owner = "76267260@N00" secret = "6032d0feb8" server
= "27" title = "Grapes, ready for the crushing" ispublic = "1" isfriend = "0"
isfamily = "0" dateupload = "1129035221" datetaken = "2005-10-03 16:09:58"
datetakengranularity = "0" latitude = "0" longitude = "0" accuracy = "0"
ownername = "thau" iconserver = "0" />
</photos>
</rsp>
Figure 15-3: Part of Flickr’s webserver XML response (I removed 29 of the photos)
Google Suggest
Google Suggest (http://labs.google.com/suggest) is just like the usual Google
search engine, but as you type a word into the search field, it presents a list
of frequently searched-for terms beginning with the letters you have already
typed, along with the number of results you’d get if you searched for each of
282 Chapter 15
those terms. For example, Figure 15-4 shows what happens when I commit
the narcissistic act of typing my name into Google’s search field. Each time
I type a letter into the search field, Google Suggest gets information from
its translation?
This is where XML comes in. XML is a standard file format ratified
by the World Wide Web Consortium in 1998 (http://www.w3.org/XML).
Since the standard’s release, XML has become the way to store and share
structured text documents.
A structured document is one that can be divided up into meaningful
components. For example, The Book of JavaScript is structured. It has chapters,
each of which has a title at the start and a summary near the end. Each
chapter also has a number of large sections indicated by headers in large
print, and these sections can be subdivided into shorter parts marked by
smaller-print headers.
Most text documents designed to be read and processed by computers
have some sort of structure; XML is a standard way to describe that structure
to a computer. XML has become so popular that all modern web browsers
come with built-in methods to read and process XML documents. Similarly,
all major programming languages used by webservers also have XML-
processing facilities. Because both web browsers and webservers under-
stand XML, they can communicate with each other by sending messages
that conform to the XML standard.
The Rules of XML
The rules for XML documents are simple and few. As another example of
an XML document, consider the XML-style English-to-Italian dictionary in
Figure 15-5.
X <?xml version = "1.0" ?>
Y <dictionary>
<word>
<english>a</english>
<translation>un, uno, una</translation>
</word>
<word>
not have an end tag, but the closing bracket has a slash before it.
The document in Figure 15-5 has several XML elements:
dictionary, word,
english, and translation. A computer program that understands XML could
read this document and know that
aardvark and a are both considered english
things, and that
english things and translation things are parts of word things,
which are parts of a
dictionary thing. Because the document is in XML, we
would not have to tell the program that the English and Italian words are
separated by a colon or that each line represents an English/Italian pair.
XML Attributes
Elements can have attributes. An element with an attribute looks like this:
<a href = "http://www.nostarch.com">No Starch Press</a> This is an XML
element named
a with an attribute named href. The value of the attribute is
quoted. Attribute names follow the same rules as element names: They must
begin with a letter and should not contain spaces or punctuation other than
periods (
.), hyphens (-), or underscores (_). Empty elements can still have
attributes:
<person name = "thau!"/>
NOTE Did you notice that the XML above is also HTML? HTML is almost a type of XML.
The main difference between it and XML is that HTML is more relaxed. For example,
the element:
<img src = "hello.gif"> is valid HTML, but not valid XML. To be valid
XML it would have to look like this:
<img src = "hello.gif"/>. Notice the slash before
the closing bracket.
upon a set of XML elements that work for them and that can be used to
structure all information within their organization or field. Varieties of XML,
called XML vocabularies or markup languages, have been invented for many
applications, such as SVG for describing two-dimensional graphics, GedML
for genealogical information, GML for geographic information, VRML for
describing objects in 3D, and SportsML, which the International Press
Telecommunications Council uses to publish sports information. Each
of these uses the rules of XML and a set of predefined element and attrib-
ute names. When using XML, you can either use existing vocabulary or
create your own.
The format of an XML document is just the tip of the XML iceberg.
There are many useful technologies built around XML, including standard
ways to search XML documents, ways to convert XML documents into PDFs
and HTML, and ways to ensure that XML documents conform to specific
vocabularies (such as GML, VRML, and the others described above).
Processing XML
XML is so popular that web browsers have built-in mechanisms for dealing
with XML documents. In fact, all of the methods used to process the DOM
(discussed in Chapter 13) can also be used to process XML documents.
The script in Figure 15-6 shows how to use some of the methods that we
used to process the DOM to create an application that looks up a word in three
dictionary files (German, Portuguese, and Italian), which I downloaded from
the Internet Dictionary Project (http://www.ilovelanguages.com/IDP).
286 Chapter 15
<html><head><title>Reading XML Documents</title>
<script type = "text/javascript">
<! hide me from older browsers
X function getTranslations(the_word) {
var languages = new Array("german","italian","portuguese");
for (var loop = 0; loop < languages.length; loop++) {
^ function findTranslation(xml_doc, the_word) {
var the_translation = "unknown";
var this_word = "";
var this_english_element = null;
_ var english_word_elements =
xml_doc.getElementsByTagName("english");
for (var loop = 0; loop < english_word_elements.length; loop++) {
this_english_element = english_word_elements[loop];
` this_word = this_english_element.firstChild.nodeValue;
if (this_word == the_word) {
a the_translation =
this_english_element.nextSibling.firstChild.nodeValue;
}
}
return the_translation;
}
XML in JavaScript and Ajax 287
// show me >
</script>
</head>
<body>
<form
onSubmit = "getTranslations(document.getElementById('theText').value);
return false;">
<input type = "text" id = "theText">
<input type = "button"
onClick = "getTranslations(document.getElementById('theText').value);
return false;" value = "Translate!">
</form>
<b>Portuguese:</b> <span id = "portuguese"></span><br>
twists. First, in Z notice that the request is getting a file whose name is the
name of the language we want to translate, with the extension .xml. Once the
request has been answered, [ retrieves the value of the request’s
responseXML
property. Then \ calls the
findTranslation() function and puts its results into
an element with an
id attribute set to the language.
NOTE Notice that the variable passed into the getTranslationsFromFile() function is used
to name both the file being read and the element into which the answer should be placed.
Using one variable in multiple contexts is a common trick. Also notice that ] puts
the string
"SEARCHING " into the element, which will soon hold the result whenever the
request object changes into any state other than 4. This is a good way to let your users
know that your page is doing something and that there is more information to come.
Visualizing the XML Document
All of the XML handling is done in the findTranslation() function (^), which
is called once the XML document has been fully loaded (after the equal sign
in \). To understand better how
findTranslation() works, see Figure 15-8,
which represents part of the XML document in Figure 15-5 graphically. The
root element of the XML document,
dictionary, appears as a node at the top
of Figure 15-8.
Figure 15-8: A graphical representation of part of the XML in Figure 15-5
The dictionary node has two child nodes, each named word, which represent
the
word elements in Figure 15-5. Each word node in Figure 15-8, in turn, has
two children, named
english and translation, representing the english and
of all the elements of the XML document with the name
"english". You can
see these elements in the XML in Figure 15-5.
Once that array of elements is returned, the function loops through the
array. Each time through the loop, the variable
this_english_element is set
to the next
english element in the array, and ` extracts the English word
between the open and close tags of the element. Line a then extracts the
translation of that
english element.
Extracting Information from XML Elements
Lines ` and a are complicated enough to deserve their own figure. Fig-
ure 15-9 is a graphic representation of the process of translating the word
aardvark. Assume that we’ve retrieved the list of elements named
english and
have looped past the first
english element. We’re now looking at the second
english element in the list, which is labeled in the figure with the box contain-
ing the words
this_english_element. The this_english_element element has a
child element that is a text node with a
nodeValue of aardvark.
Figure 15-9: A graphical representation of getting a word’s translation
Line ` extracts the value "aardvark" from this_english_element. Here’s
` again:
this_word = this_english_element.firstChild.nodeValue
This line looks at the built-in firstChild property of the XML element
stored in
this_english_element, which is a text node, and then it retrieves the
this_english_element. This word node has two children:
this_english_element and a translation node. As is true of people, any two
XML nodes that share a parent are called siblings. The sibling that comes
right after an XML node is called that node’s next sibling. The sibling next to
this_english_element is the node labeled Next Sibling of this_english_element.
This next sibling can be accessed using the built-in property
nextSibling.
Now look at a:
the_translation = this_english_element.nextSibling.firstChild.nodeValue
The code in a gets the translation of this_english_element by looking at
this_english_element, finding its next sibling (the translation node), getting
that node’s first child (the text node), and then retrieving the
nodeValue
of that text node.
Wrapping Up
Before wrapping up Figure 15-6, let’s review. A visitor has entered a word into
a text field and clicked the Translate! button. This calls
getTranslations(),
which loops through a list of languages into which the provided word should
be translated. Each time through the loop,
getTranslationFromFile() is passed
the name of a language and the word to translate. The
getTranslationFromFile()
function performs an Ajax call that reads in an XML dictionary for that
language and then calls
findTranslation() to get the translation. The
findTranslation() function does all the XML handling just covered and
returns the found translation back to the
getTranslationFromFile() function.
Now we’re ready to wrap up.
server to serve it and all the documents relating to it. Therefore, in practice,
the XML files will be served by a webserver, and Figure 15-6 should work just
as it is. The only time this Internet Explorer “feature” will cause problems is
when testing and debugging your Ajax code without using a webserver.
Fortunately, there is a simple fix: Replace [ in Figure 15-6 with this:
xml_response = new ActiveXObject("Microsoft.XMLDOM");
xml_response.loadXML(request.responseText);
These lines use a special Microsoft ActiveXObject to turn the contents of
the request’s
responseText property into XML.
Problems with White Space in XML
Some browsers, such as Firefox, like to treat blank spaces in XML documents
(including spaces used to indent tags) as text nodes. As you might imagine,
these unexpected text nodes can cause problems when using JavaScript to
navigate around an XML page.
To solve this problem, you can write some JavaScript to remove all text
nodes in the XML with
nodeValues that contain only spaces, or you can write
your JavaScript to simply ignore these blank text nodes (which usually appear
between XML elements).
For example, if Firefox reads this line of XML
<word><english>a</english> <translation>un</translation></word>
the XML document stored in request.responseXML will contain a word element
with three children: an
english element, a text node with the space between
the
english and translation elements, and the translation element. This means
that the
nextSibling property of the english element would point to the text
node rather than to the
words into Italian. The figure shows how things look in Internet Explorer
after I type
bo. On the left side you see a list of the first ten English words in
the dictionary that start with the letters bo, and on the right side are their
translations. After each keypress, the script reloads the italian.xml file and
looks for words that begin with whatever letters are in the text box. As is
typical with Ajax, there is no submit button to push; the JavaScript accesses
the information it needs and updates the page as I type.
Figure 15-10: The translation script with suggestions
XML in JavaScript and Ajax 293
NOTE For a full description of how Google Suggest works, see Chris Justice’s excellent analysis
at http://serversideguy.blogspot.com/2004/12/google-suggest-dissected.html.
The code for this neat little application can be found in Appendix D and
is available at http://www.bookofjavascript.com/Chapter15/translator.html.
It is a bit long, so I will break it up a bit as I describe it here.
NOTE Remember, this code will not work if you are testing it in Internet Explorer unless you
serve up the italian.xml file using a webserver. If you don’t have access to a webserver
and you want to test the script out, make sure to make the change described in the
section “Internet Explorer, responseXML, and Client-Side Ajax” on page 291.
Let’s begin with the text entry form:
<form>
<input type = "text" size = "55" id = "theText"
onKeyUp = "getTranslations('italian', this.value);">
<div id = "theResults" style = "width:22em; border:1px black solid;
padding-left:2px;padding-right:2px">
</div>
</form>
This form has one text input element and a div into which the results of
the script will be placed. The
ems you see in the style of the div set the width
294 Chapter 15
Finding the Translations
The findTranslations() function searches through the XML file for the
correct words to display. Figure 15-11 is a slightly abridged version:
function findTranslations(xml_doc, the_word) {
// obvious variable declarations and initializations (omitted)
var these_translations = new Array();
var english_word_elements = xml_doc.getElementsByTagName("english");
X var reg_exp = new RegExp("^" + the_word);
Y while ((loop < english_word_elements.length) && (found == false)) {
Z this_word = english_word_elements[loop].firstChild.nodeValue;
[ if (reg_exp.test(this_word)) {
the_translation =
english_word_elements[loop].nextSibling.firstChild.nodeValue;
found = true;
}
loop++;
}
\ if (found == true) {
] these_translations.push(this_word + "\t" + the_translation);
^ for (var count = loop; count < (loop + 10); count++) {
_ if (count < english_word_elements.length) {
this_word = english_word_elements[count].firstChild.nodeValue;
if (reg_exp.test(this_word)) {
the_translation =
english_word_elements[count].nextSibling.firstChild.nodeValue;
these_translations.push(this_word + "\t" + the_translation);
}
}
}
NOTE The push() method ( ]) is a handy way to add something to the end of an array;
it pushes an element to the end.
Once the retrieved word and translation have been added to the array,
it’s time to find other words that begin with the letters in the text field. The
loop in ^ begins by examining the items in the array of
english elements,
starting where the previous loop left off. It then looks at the next nine items.
Each time through the loop, the code gets the next
english element, checks
to see whether it matches the regular expression, and if so, adds it and its
translation to the
these_translations array. The code in _ makes sure the
loop ends if there are no more elements in the array of
english elements to
consider, which may happen, for example, if we are looking at words that
begin with the letter z.
When the loop in ^ ends, the function exits and returns the
these_
translations
array, which is then fed into the displayResults() function.
Displaying the Results
The function displayResults(), which displays the results, is pretty straight-
forward (as shown in Figure 15-12). The function first creates an HTML
table and then inserts that table into the
innerHTML of theResultsDiv. The
only tricky thing about this script involves changing the size of the
div so
that its border expands and contracts as the table changes size.
function displayResults(the_results) {
var display_me = "";
Y takes the string in
the_result and divides it into two parts: the part before
the tab (
\t) and the part after the tab. These pieces are stored in an array
called
splitter; splitter[0] contains the part before the tab, and splitter[1]
contains the part after the tab. The code in Z then takes these parts, creates
a string representing a row in an HTML table, and adds this string to the
display_me variable, which will contain all the rows of the table.
Once the loop completes, [ changes the
height property of the div’s
style property, making it roughly as tall as the table that it will contain. The
formula in [ gives an approximation of the
div’s height; it says that the div’s
height should equal the number of rows in the table plus a little bit for the
space between the rows. The number of rows in the table, in turn, equals the
number of items in the
the_results array. Finally, \ puts the beginning and
ending table tags on the table and puts the table into the
innerHTML of the div.
There’s a great deal more to Google Suggest, including choosing an
element from the suggestion box, caching results to make the page react
more quickly, and filling in a few letters in the text box. With the JavaScript
you know now, and a little expertise with cascading style sheets, you should
be able to add those features to your own applications.
Summary
This chapter has covered the basics of using XML with JavaScript and Ajax:
z What XML is used for
z How to format XML documents
z How Ajax applications use XML to share data