... the challenges are for the designers of these applications: to forget what we think we know about the limitations of the Web, and begin to imagine a wider, richer range of possibilities. It’s going to be fun.
—Jesse James Garrett
To know how to suggest is the great art of teaching.
—Henri-Frederic Amiel
It is characteristic of the epistemological tradition to present us with partial scenarios and then to demand whole or categorical answers as it were.
—Avrum Stroll
O! call back yesterday, bid time return.
—William Shakespeare
Objectives
In this chapter you will:
• Learn what Ajax is and why it’s important for building Rich Internet Applications.
• Use asynchronous requests to give web applications the feel of desktop applications.
• Use the XMLHttpRequest
object to manage asynchronous requests to servers and to receive asynchronous responses.
• Use XML with the DOM.
• Create a full-scale Ajax-enabled application.
16.1.1 Traditional Web Applications vs. Ajax Applications
16.1.2 Traditional Web Applications
16.2 Rich Internet Applications (RIAs) with Ajax
16.4 “Raw” Ajax Example Using the XMLHttpRequest
Object
16.4.4 XMLHttpRequest
Object Event, Properties and Methods
16.6 Creating a Full-Scale Ajax-Enabled Application
16.6.3 Interacting with a Web Service on the Server
16.6.5 Creating HTML5 Elements and Setting Event Handlers on the Fly
16.6.6 Implementing Type-Ahead
16.6.7 Implementing a Form with Asynchronous Validation
Summary | Self-Review Exercises | Answers to Self-Review Exercises | Exercises
Despite the tremendous technological growth of the Internet over the past decade, the usability of web applications has lagged behind that of desktop applications. Rich Internet Applications (RIAs) are web applications that approximate the look, feel and usability of desktop applications. Two key attributes of RIAs are performance and a rich GUI.
RIA performance comes from Ajax (Asynchronous JavaScript and XML), which uses client-side scripting to make web applications more responsive. Ajax applications separate client-side user interaction and server communication and run them in parallel, reducing the delays of server-side processing normally experienced by the user.
There are many ways to implement Ajax functionality. “Raw” Ajax uses JavaScript to send asynchronous requests to the server, then updates the page using the DOM. “Raw” Ajax is best suited for creating small Ajax components that asynchronously update a section of the page. However, when writing “raw” Ajax you need to deal directly with cross-browser portability issues, making it impractical for developing large-scale applications. These portability issues are hidden by Ajax toolkits, such as jQuery, ASP.NET Ajax and JSF’s Ajax capabilities, which provide powerful ready-to-use controls and functions that enrich web applications and simplify JavaScript coding by making it cross-browser compatible.
Traditional web applications use HTML5 forms to build GUIs that are simple by comparison with those of Windows, Macintosh and desktop systems in general. You can achieve rich GUIs in RIAs with JavaScript toolkits providing powerful ready-to-use controls and functions that enrich web applications.
Previous chapters discussed HTML5, CSS3, JavaScript, the DOM and XML. This chapter uses these technologies to build Ajax-enabled web applications. The client side of Ajax applications is written in HTML5 and CSS3 and uses JavaScript to add functionality to the user interface. XML is used to structure the data passed between server and client. We’ll also use JSON (JavaScript Object Notation) for this purpose. The Ajax component that manages interaction with the server is usually implemented with JavaScript’s XMLHttpRequest
object—commonly abbreviated as XHR. The server processing can be implemented using any server-side technology, such as PHP, ASP.NET and JavaServer Faces, each of which we cover in later chapters.
We begin with several examples that build basic Ajax applications using JavaScript and the XMLHttpRequest
object. In subsequent chapters, we use tools such as ASP.NET Ajax and JavaServer Faces to build Ajax-enabled RIAs. We also include an online introduction to jQuery.
In this section, we consider the key differences between traditional web applications and Ajax-based web applications.
Figure 16.1 presents the typical interactions between the client and the server in a traditional web application, such as one that employs a user registration form. The user first fills in the form’s fields, then submits the form (Fig. 16.1, Step 1). The browser generates a request to the server, which receives the request and processes it (Step 2). The server generates and sends a response containing the exact page that the browser will render (Step 3), which causes the browser to load the new page (Step 4) and temporarily makes the browser window blank. Note that the client waits for the server to respond and reloads the entire page with the data from the response (Step 4). While such a synchronous request is being processed on the server, the user cannot interact with the client web page. Frequent long periods of waiting, due perhaps to Internet congestion, have led some users to refer to the World Wide Web as the “World Wide Wait;” this situation has improved greatly in recent years. If the user interacts with and submits another form, the process begins again (Steps 5–8).
This model was originally designed for a web of hypertext documents—what some people call the “brochure web.” As the web evolved into a full-scale applications platform, the model shown in Fig. 16.1 yielded erratic application performance. Every full-page refresh required users to re-establish their understanding of the full-page contents. Users sought a model that would yield the responsive feel of desktop applications.
Ajax applications add a layer between the client and the server to manage communication between the two (Fig. 16.2). When the user interacts with the page, the client creates an XMLHttpRequest
object to manage a request (Step 1). The XMLHttpRequest
object sends the request to the server (Step 2) and awaits the response. The requests are asynchronous, so the user can continue interacting with the application on the client side while the server processes the earlier request concurrently. Other user interactions could result in additional requests to the server (Steps 3 and 4). Once the server responds to the original request (Step 5), the XMLHttpRequest
object that issued the request calls a client-side function to process the data returned by the server. This function—known as a callback function—uses partial page updates (Step 6) to display the data in the existing web page without reloading the entire page. At the same time, the server may be responding to the second request (Step 7) and the client side may be starting to do another partial page update (Step 8). The callback function updates only a designated part of the page. Such partial page updates help make web applications more responsive, making them feel more like desktop applications. The web application does not load a new page while the user interacts with it.
Ajax improves the user experience by making interactive web applications more responsive. Consider a registration form with a number of fields (e.g., first name, last name e-mail address, telephone number, etc.) and a Register (or Submit) button that sends the entered data to the server. Usually each field has rules that the user’s entries have to follow (e.g., valid e-mail address, valid telephone number, etc.).
When the user clicks Register, an HTML5 form sends the server all of the data to be validated (Fig. 16.3). While the server is validating the data, the user cannot interact with the page. The server finds invalid data, generates a new page identifying the errors in the form and sends it back to the client—which renders the page in the browser. Once the user fixes the errors and clicks the Register button, the cycle repeats until no errors are found, then the data is stored on the server. The entire page reloads every time the user submits invalid data.
Ajax-enabled forms are more interactive. Rather than the entire form being sent to be validated, entries can be validated individually, dynamically, as the user enters data into the fields. For example, consider a website registration form that requires a valid e-mail address. When the user enters an e-mail address into the appropriate field, then moves to the next form field to continue entering data, an asynchronous request is sent to the server to validate the e-mail address. If it’s not valid, the server sends an error message that’s displayed on the page informing the user of the problem (Fig. 16.4). By sending each entry asynchronously, the user can address each invalid entry quickly, versus making edits and resubmitting the entire form repeatedly until all entries are valid. Simple validation like this for e-mails and phone numbers can also be accomplished with HTML5’s new input
elements that you learned in Chapter 3, rather than using Ajax. Asynchronous requests could also be used to fill some fields based on previous fields (e.g., automatically filling in the “city” and “state” fields based on the ZIP code entered by the user).
The term Ajax was coined by Jesse James Garrett of Adaptive Path in February 2005, when he was presenting the previously unnamed technology to a client. The technologies of Ajax (HTML, JavaScript, CSS, the DOM and XML) had all existed for many years prior to 2005.
Asynchronous page updates can be traced back to earlier browsers. In the 1990s, Netscape’s LiveScript language made it possible to include scripts in web pages (e.g., web forms) that could run on the client. LiveScript evolved into JavaScript. In 1998, Microsoft introduced the XMLHttpRequest
object to create and manage asynchronous requests and responses. Popular applications like Flickr and Google’s Gmail use the XMLHttpRequest
object to update pages dynamically. For example, Flickr uses the technology for its text editing, tagging and organizational features; Gmail continuously checks the server for new e-mail; and Google Maps allows you to drag a map in any direction, downloading the new areas on the map without reloading the entire page.
The name Ajax immediately caught on and brought attention to its component technologies. Ajax has enabled “webtop” applications to challenge the dominance of established desktop applications. This has become increasingly significant as more and more computing moves to “the cloud.”
XMLHttpRequest
ObjectIn this section, we use the XMLHttpRequest
object to create and manage asynchronous requests. This object, which resides on the client, is the layer between the client and the server that manages asynchronous requests in Ajax applications. It’s supported on most browsers, though they may implement it differently—a common issue with browsers. To initiate an asynchronous request (shown in Fig. 16.5), you create an instance of the XMLHttpRequest
object, then use its open
method to set up the request and its send
method to initiate the request. We summarize the XMLHttpRequest
properties and methods in Figs. 16.6–16.7.
Figure 16.5 presents an Ajax application in which the user interacts with the page by moving the mouse over book-cover images; a detailed code walkthrough follows the figure. We use the mouseover
and mouseout
events to trigger events when the user moves the mouse over and out of an image, respectively. The mouseover
event calls function getContent
with the URL of the document containing the book’s description. The function makes this request asynchronously using an XMLHttpRequest
object. When the XMLHttpRequest
object receives the response, the book description is displayed below the book images. When the user moves the mouse out of the image, the mouseout
event calls function clearContent
to clear the display box. These tasks are accomplished without reloading the page on the client. You can test-drive this example at http://test.deitel.com/iw3htp5/ch16/fig16_05/SwitchContent.html.
When an Ajax application requests a file from a server, such as an HTML5 document or an image, the browser typically caches that file. Subsequent requests for the same file can load it from the browser’s cache rather than making the round trip to the server again.
Software Engineering Observation 16.1
For security purposes, the XMLHttpRequest
object doesn’t allow a web application to request resources from domains other than the one that served the application. For this reason, the web application and its resources must reside on the same web server (this could be a web server on your local computer). This is commonly known as the same origin policy (SOP). SOP aims to close a vulnerability called cross-site scripting, also known as XSS, which allows an attacker to compromise a website’s security by injecting a malicious script onto the page from another domain. To get content from another domain securely, you can implement a server-side proxy—an application on the web application’s web server—that can make requests to other servers on the web application’s behalf.
The function getContent
(lines 46–63) sends the asynchronous request. Line 51 creates the XMLHttpRequest
object, which manages the asynchronous request. We store the object in the global variable asyncRequest
(declared at line 13) so that it can be accessed anywhere in the script. You can test this web page at test.deitel.com/iw3htp5/ch16/fig16_05/SwitchContent.html.
Line 56 calls the XMLHttpRequest open
method to prepare an asynchronous GET
request. In this example, the url
parameter specifies the address of an HTML document containing the description of a particular book. When the third argument is true
, the request is asynchronous. The URL is passed to function getContent
in response to the onmouseover
event for each image. Line 57 send
s the asynchronous request to the server by calling the XMLHttpRequest send
method. The argument null
indicates that this request is not submitting data in the body of the request.
1 <!DOCTYPE html>
2
3 <!-- Fig. 16.5: SwitchContent.html -->
4 <!-- Asynchronously display content without reloading the page. -->
5 <html>
6 <head>
7 <meta charset = "utf-8">
8 <style type = "text/css">
9 .box { border: 1px solid black; padding: 10px }
10 </style>
11 <title>Switch Content Asynchronously</title>
12 <script>
13 var asyncRequest; // variable to hold XMLHttpRequest object
14
15 // set up event handlers
16 function registerListeners()
17 {
18 var img;
19 img = document.getElementById( "cpphtp" );
20 img.addEventListener( "mouseover",
21 function() { getContent( "cpphtp8.html" ); }, false );
22 img.addEventListener( "mouseout", clearContent, false );
23 img = document.getElementById( "iw3htp" );
24 img.addEventListener( "mouseover",
25 function() { getContent( "iw3htp.html" ); }, false );
26 img.addEventListener( "mouseout", clearContent, false );
27 img = document.getElementById( "jhtp" );
28 img.addEventListener( "mouseover",
29 function() { getContent( "jhtp.html" ); }, false );
30 img.addEventListener( "mouseout", clearContent, false );
31 img = document.getElementById( "vbhtp" );
32 img.addEventListener( "mouseover",
33 function() { getContent( "vbhtp.html" ); }, false );
34 img.addEventListener( "mouseout", clearContent, false );
35 img = document.getElementById( "vcshtp" );
36 img.addEventListener( "mouseover",
37 function() { getContent( "vcshtp.html" ); }, false );
38 img.addEventListener( "mouseout", clearContent, false );
39 img = document.getElementById( "javafp" );
40 img.addEventListener( "mouseover",
41 function() { getContent( "javafp.html" ); }, false );
42 img.addEventListener( "mouseout", clearContent, false );
43 } // end function registerListeners
44
45 // set up and send the asynchronous request.
46 function getContent( url )
47 {
48 // attempt to create XMLHttpRequest object and make the request
49 try
50 {
51 asyncRequest = new XMLHttpRequest(); // create request object
52
53 // register event handler
54 asyncRequest.addEventListener(
55 "readystatechange", stateChange, false);
56 asyncRequest.open( "GET", url, true ); // prepare the request
57 asyncRequest.send( null ); // send the request
58 } // end try
59 catch ( exception )
60 {
61 alert( "Request failed." );
62 } // end catch
63 } // end function getContent
64
65 // displays the response data on the page
66 function stateChange()
67 {
68 if ( asyncRequest.readyState == 4 && asyncRequest.status == 200 )
69 {
70 document.getElementById( "contentArea" ).innerHTML =
71 asyncRequest.responseText; // places text in contentArea
72 } // end if
73 } // end function stateChange
74
75 // clear the content of the box
76 function clearContent()
77 {
78 document.getElementById( "contentArea" ).innerHTML = "";
79 } // end function clearContent
80
81 window.addEventListener( "load", registerListeners, false );
82 </script>
83 </head>
84 <body>
85 <h1>Mouse over a book for more information.</h1>
86 <img id = "cpphtp" alt = "C++ How to Program book cover"
87 src = "http://test.deitel.com/images/thumbs/cpphtp8.jpg">
88 <img id = "iw3htp" alt = "Internet & WWW How to Program book cover"
89 src = "http://test.deitel.com/images/thumbs/iw3htp5.jpg">
90 <img id = "jhtp" alt = "Java How to Program book cover"
91 src = "http://test.deitel.com/images/thumbs/jhtp9.jpg">
92 <img id = "vbhtp" alt = "Visual Basic 2010 How to Program book cover"
93 src = "http://test.deitel.com/images/thumbs/vb2010htp.jpg">
94 <img id = "vcshtp" alt = "Visual C# 2010 How to Program book cover"
95 src = "http://test.deitel.com/images/thumbs/vcsharp2010htp.jpg">
96 <img id = "javafp" alt = "Java for Programmers book cover"
97 src = "http://test.deitel.com/images/thumbs/javafp.jpg">
98 <div class = "box" id = "contentArea"></div>
99 </body>
100 </html>
Lines 59–62 introduce exception handling. An exception is an indication of a problem that occurs during a program’s execution. The name “exception” implies that the problem occurs infrequently. Exception handling enables you to create applications that can handle (i.e., resolve) exceptions—often allowing a program to continue executing as if no problem had been encountered.
Lines 49–58 contain a try
block, which encloses the code that might cause an exception and the code that should not execute if an exception occurs (i.e., if an exception occurs in a statement of the try
block, the remaining code in the try
block is skipped). A try
block consists of the keyword try
followed by a block of code enclosed in curly braces ({}
). If there’s a problem sending the request—e.g., if a user tries to access the page using an older browser that does not support XMLHttpRequest
—the try
block terminates immediately and a catch
block (also called a catch
clause or exception handler) catches (i.e., receives) and handles the exception. The catch
block (lines 59–62) begins with the keyword catch
and is followed by a parameter in parentheses—called the exception parameter—and a block of code enclosed in curly braces. The exception parameter’s name (exception
in this example) enables the catch
block to interact with a caught exception object (for example, to obtain the name of the exception or an exception-specific error message via the exception object’s name
and message
properties, respectively). In this case, we simply display our own error message "Request Failed"
and terminate the getContent
function. The request can fail because a user accesses the web page with an older browser or the content that’s being requested is located on a different domain.
The stateChange
function (lines 66–73) is the callback function that’s called when the client receives the response data. Lines 54–55 register function stateChange
as the event handler for the XMLHttpRequest
object’s readystatechange
event. Whenever the request makes progress, the XMLHttpRequest
object calls the readystatechange
event handler. This progress is monitored by the readyState
property, which has a value from 0 to 4. The value 0 indicates that the request is not initialized and the value 4 indicates that the request is complete—all the values for this property are summarized in Fig. 16.6. If the request completes successfully (line 68), lines 70–71 use the XMLHttpRequest
object’s responseText
property to obtain the response data and place it in the div
element named contentArea
(defined at line 98). We use the DOM’s getElementById
method to get this div
element, and use the element’s innerHTML
property to place the content in the div
.
XMLHttpRequest
Object Event, Properties and MethodsFigures 16.6 and 16.7 summarize some of the XMLHttpRequest
object’s properties and methods, respectively. The properties are crucial to interacting with asynchronous requests. The methods initialize, configure and send asynchronous requests.
When passing structured data between the server and the client, Ajax applications often use XML because it’s easy to generate and parse. When the XMLHttpRequest
object receives XML data, it parses and stores the data as an XML DOM object in the responseXML
property. The example in Fig. 16.8 asynchronously requests from a server XML documents containing URLs of book-cover images, then displays the images in the page. The code that configures the asynchronous request is the same as in Fig. 16.5. You can test-drive this application at
http://test.deitel.com/iw3htp5/ch16/fig16_08/PullImagesOntoPage.html
1 <!DOCTYPE html>
2
3 <!-- Fig. 16.8: PullImagesOntoPage.html -->
4 <!-- Image catalog that uses 1Ajax to request XML data asynchronously. -->
5 <html>
6 <head>
7 <meta charset="utf-8">
8 <title> Pulling Images onto the Page </title>
9 <style type = "text/css">
10 li { display: inline-block; padding: 4px; width: 120px; }
11 img { border: 1px solid black }
12 </style>
13 <script>
14 var asyncRequest; // variable to hold XMLHttpRequest object
15
16 // set up and send the asynchronous request to get the XML file
17 function getImages( url )
18 {
19 // attempt to create XMLHttpRequest object and make the request
20 try
21 {
22 asyncRequest = new XMLHttpRequest(); // create request object
23
24 // register event handler
25 asyncRequest.addEventListener(
26 "readystatechange", processResponse, false);
27 asyncRequest.open( "GET", url, true ); // prepare the request
28 asyncRequest.send( null ); // send the request
29 } // end try
30 catch ( exception )
31 {
32 alert( 'Request Failed' );
33 } // end catch
34 } // end function getImages
35
36 // parses the XML response; dynamically creates an undordered list and
37 // populates it with the response data; displays the list on the page
38 function processResponse()
39 {
40 // if request completed successfully and responseXML is non-null
41 if ( asyncRequest.readyState == 4 && asyncRequest.status == 200 &&
42 asyncRequest.responseXML )
43 {
44 clearImages(); // prepare to display a new set of images
45
46 // get the covers from the responseXML
47 var covers = asyncRequest.responseXML.getElementsByTagName(
48 "cover" )
49
50 // get base URL for the images
51 var baseUrl = asyncRequest.responseXML.getElementsByTagName(
52 "baseurl" ).item( 0 ).firstChild.nodeValue;
53
54 // get the placeholder div element named covers
55 var output = document.getElementById( "covers" );
56
57 // create an unordered list to display the images
58 var imagesUL = document.createElement( "ul" );
59
60 // place images in unordered list
61 for ( var i = 0; i < covers.length; ++i )
62 {
63 var cover = covers.item( i ); // get a cover from covers array
64
65 // get the image filename
66 var image = cover.getElementsByTagName( "image" ).
67 item( 0 ).firstChild.nodeValue;
68
69 // create li and img element to display the image
70 var imageLI = document.createElement( "li" );
71 var imageTag = document.createElement( "img" );
72
73 // set img element's src attribute
74 imageTag.setAttribute( "src", baseUrl + escape( image ) );
75 imageLI.appendChild( imageTag ); // place img in li
76 imagesUL.appendChild( imageLI ); // place li in ul
77 } // end for statement
78
79 output.appendChild( imagesUL ); // append ul to covers div
80 } // end if
81 } // end function processResponse
82
83 // clears the covers div
84 function clearImages()
85 {
86 document.getElementById( "covers" ).innerHTML = "";
87 } // end function clearImages
88
89 // register event listeners
90 function registerListeners()
91 {
92 document.getElementById( "all" ).addEventListener(
93 "click", function() { getImages( "all.xml" ); }, false );
94 document.getElementById( "simply" ).addEventListener(
95 "click", function() { getImages( "simply.xml" ); }, false );
96 document.getElementById( "howto" ).addEventListener(
97 "click", function() { getImages( "howto.xml" ); }, false );
98 document.getElementById( "dotnet" ).addEventListener(
99 "click", function() { getImages( "dotnet.xml" ); }, false );
100 document.getElementById( "javaccpp" ).addEventListener(
101 "click", function() { getImages( "javaccpp.xml" ); }, false );
102 document.getElementById( "none" ).addEventListener(
103 "click", clearImages, false );
104 } // end function registerListeners
105
106 window.addEventListener( "load", registerListeners, false );
107 </script>
108 </head>
109 <body>
110 <input type = "radio" name ="Books" value = "all"
111 id = "all"> All Books
112 <input type = "radio" name = "Books" value = "simply"
113 id = "simply"> Simply Books
114 <input type = "radio" name = "Books" value = "howto"
115 id = "howto"> How to Program Books
116 <input type = "radio" name = "Books" value = "dotnet"
117 id = "dotnet"> .NET Books
118 <input type = "radio" name = "Books" value = "javaccpp"
119 id = "javaccpp"> Java/C/C++ Books
120 <input type = "radio" checked name = "Books" value = "none"
121 id = "none"> None
122 <div id = "covers"></div>
123 </body>
124 </html>
When the XMLHttpRequest
object receives the response, it invokes the callback function processResponse
(lines 38–81). We use XMLHttpRequest
object’s responseXML
property to access the XML returned by the server. Lines 41–42 check that the request was successful and that the responseXML
property is not empty. The XML file that we requested includes a baseURL
node that contains the address of the image directory and a collection of cover
nodes that contain image filenames. responseXML
is a document object, so we can extract data from it using the XML DOM functions. Lines 47–52 use the DOM’s method getElementsByTagName
to extract all the image filenames from cover
nodes and the URL of the directory from the baseURL
node. Since the baseURL
has no child nodes, we use item(0).firstChild.nodeValue
to obtain the directory’s address and store it in variable baseURL
. The image filenames are stored in the covers
array.
As in Fig. 16.5 we have a placeholder div
element (line 122) to specify where the image table will be displayed on the page. Line 55 stores the div
in variable output
, so we can fill it with content later in the program.
Lines 58–77 generate an HTML5 unordered list dynamically, using the createElement
, setAttribute
and appendChild
HTML5 DOM methods. Method createElement
creates an HTML5 element of the specified type. Method appendChild
inserts one HTML5 element into another. Line 58 creates the ul
element. Each iteration of the for
statement obtains the filename of the image to be inserted (lines 63–67), creates an li
element to hold the image (line 70) and creates an <img>
element (line 71). Line 74 sets the image’s src
attribute to the image’s URL, which we build by concatenating the filename to the base URL of the HTML5 document. Lines 75–76 insert the <img>
element into the li
element and the li
element into the ul
element. Once all the images have been inserted into the unordered list, the list is inserted into the placeholder element covers
that’s referenced by variable output
(line 79). This element is located on the bottom of the web page.
Function clearImages
(lines 84–87) is called to clear images when the user clicks the None radio button. The text is cleared by setting the innerHTML
property of the placeholder element to an empty string.
Our next example demonstrates additional Ajax capabilities. The web application interacts with a web service to obtain data and to modify data in a server-side database. The web application and server communicate with a data format called JSON (JavaScript Object Notation). In addition, the application demonstrates server-side validation that occurs in parallel with the user interacting with the web application. You can test-drive the application at http://test.deitel.com/iw3htp5/ch16/fig16_09-10/AddressBook.html.
JSON (JavaScript Object Notation)—a simple way to represent JavaScript objects as strings—is a simpler alternative to XML for passing data between the client and the server. Each object in JSON is represented as a list of property names and values contained in curly braces, in the following format:
{ "propertyName1" : value1, "propertyName2": value2 }
Arrays are represented in JSON with square brackets in the following format:
[ value1, value2, value3 ]
Each value can be a string, a number, a JSON representation of an object, true, false
or null
. You can convert JSON strings into JavaScript objects with JavaScript’s JSON.parse
function. JSON strings are easier to create and parse than XML and require fewer bytes. For these reasons, JSON is commonly used to communicate in client/server interaction.
The previous examples in this chapter requested data from files on the server. The example in Figs. 16.9–16.10 is an address–book application that communicates with a server-side web service. The application uses server-side processing to give the page the functionality and usability of a desktop application. We use JSON to encode server-side responses and to create objects on the fly. Figure 16.9 presents the HTML5 document. Figure 16.10 presents the JavaScript.
Initially the address book loads a list of entries, each containing a first and last name (Fig. 16.9(a)). Each time the user clicks a name, the address book uses Ajax functionality to load the person’s address from the server and expand the entry without reloading the page (Fig. 16.9(b))—and it does this in parallel with allowing the user to click other names.
The application allows the user to search the address book by typing a last name. As the user enters each keystroke, the application asynchronously calls the server to obtain the list of names in which the last name starts with the characters the user has entered so far (Fig. 16.9(c), (d) and (e))—a popular feature called type-ahead.
The application also enables the user to add another entry to the address book by clicking the Add an Entry button (Fig. 16.9(f)). The application displays a form that enables live field validation.
As the user fills out the form, the ZIP code is eventually entered, and when the user tabs to the next field, the blur
event handler for the ZIP-code field makes an Ajax call to the server. The server then validates the ZIP code, uses the valid zip code to obtain the corresponding city and state from a ZIP-code web service and returns this information to the client (Fig. 16.9(g). [If the ZIP code were invalid, the web service would return an error to the server, which would then send an error message back to the client.]
When the user enters the telephone number and moves the cursor out of the Telephone: field, the blur
event handler for that field uses an Ajax call to the server to validate the telephone number—if it were invalid, the server would return an error message to the client.
When the Submit button is clicked, the button’s event handler determines that some required data is missing and displays the message “First Name and Last Name must have a value.” at the bottom of the screen (Fig. 16.9(h)). The user enters the missing data and clicks Submit again (Fig. 16.9(i)). The client-side code revalidates the data, determines that it’s correct and sends it to the server. The server performs its own validation, then returns the updated address book, which is displayed on the client, with the new name added in (Fig. 16.9(j)).
1 <!DOCTYPE html>
2
3 <!-- Fig. 16.9 addressbook.html -->
4 <!-- Ajax enabled address book application. -->
5 <html>
6 <head>
7 <meta charset="utf-8">
8 <title>Address Book</title>
9 <link rel = "stylesheet" type = "text/css" href = "style.css">
10 <script src = "AddressBook.js"></script>
11 </head>
12 <body>
13 <div>
14 <input id = "addressBookButton" type = "button"
15 value = "Address Book">
16 <input id = "addEntryButton" type = "button"
17 value = "Add an Entry">
18 </div>
19 <div id = "addressBook"">
20 <p>Search By Last Name: <input id = "searchInput"></p>
21 <div id = "Names"></div>
22 </div>
23 <div id = "addEntry" style = "display : none">
24 <p><label>First Name:</label> <input id = "first"></p>
25 <p><label>Last Name:</label> <input id = "last"></p>
26 <p class = "head">Address:</p>
27 <p><label>Street:</label> <input id = "street"></p>
28 <p><label>City:</label> <span id = "city" class = "validator">
29 </span></p>
30 <p><label>State:</label> <span id = "state" class = "validator">
31 </span></p>
32 <p><label>Zip:</label> <input id = "zip">
33 <span id = "validateZip" class = "validator"></span></p>
34 <p><label>Telephone:</label> <input id = "phone">
35 <span id = "validatePhone" class = "validator"></span></p>
36 <p><input id = "submitButton" type = "button" value = "Submit"></p>
37 <div id = "success" class = "validator"></div>
38 </div>
39 </body>
40 </html>
1 // Fig. 16.10 addressbook.js
2 // Ajax-enabled address-book JavaScript code
3 // URL of the web service
4 var webServiceUrl = "/AddressBookWebService/Service.svc";
5
6 var phoneValid = false; // indicates if the telephone is valid
7 var zipValid = false; //indicates if the ZIP code is valid
8
9 // get a list of names from the server and display them
10 function showAddressBook()
11 {
12 // hide the "addEntry" form and show the address book
13 document.getElementById( "addEntry" ).style.display = "none";
14 document.getElementById( "addressBook" ).style.display = "block";
15
16 callWebService( "/getAllNames", parseData );
17 } // end function showAddressBook
18
19 // send the asynchronous request to the web service
20 function callWebService( methodAndArguments, callBack )
21 {
22 // build request URL string
23 var requestUrl = webServiceUrl + methodAndArguments;
24
25 // attempt to send the asynchronous request
26 try
27 {
28 var asyncRequest = new XMLHttpRequest(); // create request
29
30 // set up callback function and store it
31 asyncRequest.addEventListener("readystatechange",
32 function() { callBack( asyncRequest ); }, false);
33
34 // send the asynchronous request
35 asyncRequest.open( "GET", requestUrl, true );
36 asyncRequest.setRequestHeader("Accept",
37 "application/json; charset=utf-8" );
38 asyncRequest.send(); // send request
39 } // end try
40 catch ( exception )
41 {
42 alert ( "Request Failed" );
43 } // end catch
44 } // end function callWebService
45
46 // parse JSON data and display it on the page
47 function parseData( asyncRequest )
48 {
49 // if request has completed successfully, process the response
50 if ( asyncRequest.readyState == 4 && asyncRequest.status == 200 )
51 {
52 // convert the JSON string to an Object
53 var data = JSON.parse(asyncRequest.responseText);
54 displayNames( data ); // display data on the page
55 } // end if
56 } // end function parseData
57
58 // use the DOM to display the retrieved address-book entries
59 function displayNames( data )
60 {
61 // get the placeholder element from the page
62 var listBox = document.getElementById( "Names" );
63 listBox.innerHTML = ""; // clear the names on the page
64
65 // iterate over retrieved entries and display them on the page
66 for ( var i = 0; i < data.length; ++i )
67 {
68 // dynamically create a div element for each entry
69 // and a fieldset element to place it in
70 var entry = document.createElement( "div" );
71 var field = document.createElement( "fieldset" );
72 entry.onclick = function() { getAddress( this, this.innerHTML ); };
73 entry.id = i; // set the id
74 entry.innerHTML = data[ i ].First + " " + data[ i ].Last;
75 field.appendChild( entry ); // insert entry into the field
76 listBox.appendChild( field ); // display the field
77 } // end for
78 } // end function displayAll
79
80 // search the address book for input
81 // and display the results on the page
82 function search( input )
83 {
84 // get the placeholder element and delete its content
85 var listBox = document.getElementById( "Names" );
86 listBox.innerHTML = ""; // clear the display box
87
88 // if no search string is specified, all the names are displayed
89 if ( input == "" ) // if no search value specified
90 {
91 showAddressBook(); // Load the entire address book
92 } // end if
93 else
94 {
95 callWebService( "/search/" + input, parseData );
96 } // end else
97 } // end function search
98
99 // Get address data for a specific entry
100 function getAddress( entry, name )
101 {
102 // find the address in the JSON data using the element's id
103 // and display it on the page
104 var firstLast = name.split(" "); // convert string to array
105 var requestUrl = webServiceUrl + "/getAddress/"
106 + firstLast[ 0 ] + "/" + firstLast[ 1 ];
107
108 // attempt to send an asynchronous request
109 try
110 {
111 // create request object
112 var asyncRequest = new XMLHttpRequest();
113
114 // create a callback function with 2 parameters
115 asyncRequest.addEventListener("readystatechange",
116 function() { displayAddress( entry, asyncRequest ); }, false);
117
118 asyncRequest.open( "GET", requestUrl, true );
119 asyncRequest.setRequestHeader("Accept",
120 "application/json; charset=utf-8"); // set response datatype
121 asyncRequest.send(); // send request
122 } // end try
123 catch ( exception )
124 {
125 alert ( "Request Failed." );
126 } // end catch
127 } // end function getAddress
128
129 // clear the entry's data
130 function displayAddress( entry, asyncRequest )
131 {
132 // if request has completed successfully, process the response
133 if ( asyncRequest.readyState == 4 && asyncRequest.status == 200 )
134 {
135 // convert the JSON string to an object
136 var data = JSON.parse(asyncRequest.responseText);
137 var name = entry.innerHTML // save the name string
138 entry.innerHTML = name + "<br>" + data.Street +
139 "<br>" + data.City + ", " + data.State
140 + ", " + data.Zip + "<br>" + data.Telephone;
141
142 // change event listener
143 entry.onclick = function() { clearField( entry, name ); };
144 } // end if
145 } // end function displayAddress
146
147 // clear the entry's data
148 function clearField( entry, name )
149 {
150 entry.innerHTML = name; // set the entry to display only the name
151 entry.onclick = function() { getAddress( entry, name ); };
152 } // end function clearField
153
154 // display the form that allows the user to enter more data
155 function addEntry()
156 {
157 document.getElementById( "addressBook" ).style.display = "none";
158 document.getElementById( "addEntry" ).style.display = "block";
159 } // end function addEntry
160
161 // send the ZIP code to be validated and to generate city and state
162 function validateZip( zip )
163 {
164 callWebService ( "/validateZip/" + zip, showCityState );
165 } // end function validateZip
166
167 // get city and state that were generated using the zip code
168 // and display them on the page
169 function showCityState( asyncRequest )
170 {
171 // display message while request is being processed
172 document.getElementById( "validateZip" ).
173 innerHTML = "Checking zip...";
174
175 // if request has completed successfully, process the response
176 if ( asyncRequest.readyState == 4 )
177 {
178 if ( asyncRequest.status == 200 )
179 {
180 // convert the JSON string to an object
181 var data = JSON.parse(asyncRequest.responseText);
182
183 // update ZIP-code validity tracker and show city and state
184 if ( data.Validity == "Valid" )
185 {
186 zipValid = true; // update validity tracker
187
188 // display city and state
189 document.getElementById( "validateZip" ).innerHTML = "";
190 document.getElementById( "city" ).innerHTML = data.City;
191 document.getElementById( "state" ).
192 innerHTML = data.State;
193 } // end if
194 else
195 {
196 zipValid = false; // update validity tracker
197 document.getElementById( "validateZip" ).
198 innerHTML = data.ErrorText; // display the error
199
200 // clear city and state values if they exist
201 document.getElementById( "city" ).innerHTML = "";
202 document.getElementById( "state" ).innerHTML = "";
203 } // end else
204 } // end if
205 else if ( asyncRequest.status == 500 )
206 {
207 document.getElementById( "validateZip" ).
208 innerHTML = "Zip validation service not avaliable";
209 } // end else if
210 } // end if
211 } // end function showCityState
212
213 // send the telephone number to the server to validate format
214 function validatePhone( phone )
215 {
216 callWebService( "/validateTel/" + phone, showPhoneError );
217 } // end function validatePhone
218
219 // show whether the telephone number has correct format
220 function showPhoneError( asyncRequest )
221 {
222 // if request has completed successfully, process the response
223 if ( asyncRequest.readyState == 4 && asyncRequest.status == 200 )
224 {
225 // convert the JSON string to an object
226 var data = JSON.parse(asyncRequest.responseText);
227
228 if ( data.ErrorText != "Valid Telephone Format" )
229 {
230 phoneValid = false; // update validity tracker
231 document.getElementById( "validatePhone" ).innerHTML =
232 data.ErrorText; // display the error
233 } // end if
234 else
235 {
236 phoneValid = true; // update validity tracker
237 } // end else
238 } // end if
239 } // end function showPhoneError
240
241 // enter the user's data into the database
242 function saveForm()
243 {
244 // retrieve the data from the form
245 var first = document.getElementById( "first" ).value;
246 var last = document.getElementById( "last" ).value;
247 var street = document.getElementById( "street" ).value;
248 var city = document.getElementById( "city" ).innerHTML;
249 var state = document.getElementById( "state" ).innerHTML;
250 var zip = document.getElementById( "zip" ).value;
251 var phone = document.getElementById( "phone" ).value;
252
253 // check if data is valid
254 if ( !zipValid || !phoneValid )
255 {
256 // display error message
257 document.getElementById( "success" ).innerHTML =
258 "Invalid data entered. Check form for more information";
259 } // end if
260 else if ( ( first == "" ) || ( last == "" ) )
261 {
262 // display error message
263 document.getElementById("success").innerHTML =
264 "First Name and Last Name must have a value.";
265 } // end if
266 else
267 {
268 // hide the form and show the address book
269 document.getElementById( "addEntry" ).style.display = "none";
270 document.getElementById( "addressBook" ).style.display = "block";
271
272 // call the web service to insert data into the database
273 callWebService( "/addEntry/" + first + "/" + last + "/" + street +
274 "/" + city + "/" + state + "/" + zip + "/" + phone, parseData );
275 } // end else
276 } // end function saveForm
277
278 // register event listeners
279 function start()
280 {
281 document.getElementById( "addressBookButton" ).addEventListener(
282 "click", showAddressBook, false );
283 document.getElementById( "addEntryButton" ).addEventListener(
284 "click", addEntry, false );
285 document.getElementById( "searchInput" ).addEventListener(
286 "keyup", function() { search( this.value ); }, false );
287 document.getElementById( "zip" ).addEventListener(
288 "blur", function() { validateZip( this.value ); }, false );
289 document.getElementById( "phone" ).addEventListener(
290 "blur", function() { validatePhone( this.value ); }, false );
291 document.getElementById( "submitButton" ).addEventListener(
292 "click", saveForm, false );
293
294 showAddressBook();
295 } // end function start
296
297 window.addEventListener( "load", start, false );
When the page loads, the load
event (Fig. 16.10, line 297) calls the start
function (lines 279–295) to register various event listeners and to call showAddressBook
, which loads the address book onto the page. Function showAddressBook
(lines 10–17) shows the addressBook
element and hides the addEntry
element (lines 13–14). Then it calls function callWebService
to make an asynchronous request to the server (line 16). Our program uses an ASP.NET REST web service that we created for this example to do the server-side processing. The web service contains a collection of methods, including getAllNames
, that can be called from a web application. To invoke a method you specify the web service URL followed by a forward slash (/
), the name of the method to call, a forward slash and the arguments separated by forward slashes. Function callWebService
requires a string containing the method to call on the server and the arguments to the method in the format described above. In this case, the function we’re invoking on the server requires no arguments, so line 16 passes the string "/getAllNames"
as the first argument to callWebService
.
Function callWebService
(lines 20–44) contains the code to call our web service, given a string containing the web-service method to call and the arguments to that method (if any), and the name of a callback function. The web-service method to call and its arguments are appended to the request URL (line 23). In this first call, we do not pass any parameters because the web method that returns all the entries requires none. However, future web method calls will include arguments in the methodAndArguments
parameter. Lines 28–38 prepare and send the request, using functionality similar to that in the previous two examples. There are many types of user interactions in this application, each requiring a separate asynchronous request. For this reason, we pass the appropriate asyncRequest
object as an argument to the function specified by the callBack
parameter. However, event handlers cannot receive arguments, so lines 31–32 register an anonymous function for asyncRequest
’s readystatechange
event. When this anonymous function gets called, it calls function callBack
and passes the asyncRequest
object as an argument. Lines 36–37 set an Accept
request header to receive JSON-formatted data.
Each of our web service’s methods in this example returns a JSON representation of an object or array of objects. For example, when the web application requests the list of names in the address book, the list is returned as a JSON array, as shown in Fig. 16.11. Each object in Fig. 16.11 has the attributes first
and last
.
1 [ { "first": "Cheryl", "last": "Black" },
2 { "first": "James", "last": "Blue" },
3 { "first": "Mike", "last": "Brown" },
4 { "first": "Meg", "last": "Gold" } ]
When the XMLHttpRequest
object receives the response, it calls function parseData
(Fig. 16.10, lines 47–56). Line 53 calls the JSON.parse
function, which converts the JSON string into a JavaScript object. Then line 54 calls function displayNames
(lines 59–78), which displays the first and last name of each address–book entry passed to it. Lines 62–63 use the HTML5 DOM to store the placeholder div
element Names
in the variable listbox
and clear its content. Once parsed, the JSON string of address–book entries becomes an array, which this function traverses (lines 66–77).
Line 71 uses an HTML5 fieldset
element to create a box in which the entry will be placed. Line 72 registers an anonymous function that calls getAddress
as the onclick
event handler for the div
created in line 70. This enables the user to expand each address–book entry by clicking it. The arguments to getAddress
are generated dynamically and not evaluated until the getAddress
function is called. This enables each function to receive arguments that are specific to the entry the user clicked. Line 74 displays the names on the page by accessing the first
(first name) and last
(last name) fields of each element of the data
array. To determine which address the user clicked, we introduce the this
keyword. The meaning of this
depends on its context. In an event-handling function, this
refers to the DOM object on which the event occurred. Our function uses this
to refer to the clicked entry. The this
keyword allows us to use one event handler to apply a change to one of many DOM elements, depending on which one received the event.
Function getAddress
(lines 100–127) is called when the user clicks an entry. This request must keep track of the entry where the address is to be displayed on the page. Lines 115–116 set as the callback function an anonymous function that calls displayAddress
with the entry
element as an argument. Once the request completes successfully, lines 136–140 parse the response and display the addresses. Line 143 updates the div
’s onclick
event handler to hide the address data when that div
is clicked again by the user. When the user clicks an expanded entry, function clearField
(lines 148–152) is called. Lines 150–151 reset the entry’s content and its onclick
event handler to the values they had before the entry was expanded.
You’ll notice that we registered click-event handlers for the items in the fieldset
by using the onclick
property of each item, rather than the addEventListener
method. We did this for simplicity in this example because we want to modify the event handler for each item’s click event based on whether the item is currently displaying just the contact’s name or its complete address. Each call to addEventListener
adds another event listener to the object on which it’s called—for this example, that could result in many event listeners being called for one entry that the user clicks repeatedly. Using the onclick
property allows you to set only one listener at a time for a paticular event, which makes it easy for us to switch event listeners as the user clicks each item in the contact list.
The input
element declared in line 20 of Fig. 16.9 enables the user to search the address book by last name. As soon as the user starts typing in the input box, the keyup
event handler (registered at lines 285–286 in Fig. 16.10) calls the search
function (lines 82–97), passing the input
element’s value as an argument. The search
function performs an asynchronous request to locate entries with last names that start with its argument value. When the response is received, the application displays the matching list of names. Each time the user changes the text in the input box, function search
is called again to make another asynchronous request.
The search
function first clears the address–book entries from the page (lines 85–86). If the input
argument is the empty string, line 91 displays the entire address book by calling function showAddressBook
. Otherwise line 95 sends a request to the server to search the data. Line 95 creates a string to represent the method and argument that callWebService
will append to the request URL. When the server responds, callback function parseData
is invoked, which calls function displayNames
to display the results on the page.
When the Add an Entry button in the HTML5 document is clicked, the addEntry
function (lines 155–159) is called, which hides the addressBook div
and shows the addEntry div
that allows the user to add a person to the address book. The addEntry div
in the HTML5 document contains a set of entry fields, some of which have event handlers (registered in the JavaScript start
function) that enable validation that occurs asynchronously as the user continues to interact with the page. When a user enters a ZIP code, then moves the cursor to another field, the validateZip
function (lines 162–165) is called. This function calls an external web service to validate the ZIP code. If it’s valid, that external web service returns the corresponding city and state. Line 164 calls the callWebService
function with the appropriate method and argument, and specifies showCityState
(lines 169–211) as the callback function.
ZIP-code validation can take significant time due to network delays. The function showCityState
is called every time the request object’s readyState
property changes. Until the request completes, lines 172–173 display "Checking zip..."
on the page. After the request completes, line 181 converts the JSON response text to an object. The response object has four properties—Validity
, ErrorText
, City
and State
. If the request is valid, line 186 updates the zipValid
script variable that keeps track of ZIP-code validity, and lines 189–192 show the city and state that the server generated using the ZIP code. Otherwise lines 196–198 update the zipValid
variable and show the error code. Lines 201–202 clear the city and state elements. If our web service fails to connect to the ZIP-code validator web service, lines 207–208 display an appropriate error message.
Similarly, when the user enters the telephone number, the function validatePhone
(lines 214–217) sends the phone number to the server. Once the server responds, the showPhoneError
function (lines 220–239) updates the validatePhone
script variable and shows the error message, if the web service returned one.
When the Submit button is clicked, the saveForm
function is called (lines 242–276). Lines 245–251 retrieve the data from the form. Lines 254–259 check if the ZIP code and telephone number are valid, and display the appropriate error message in the Success
element on the bottom of the page. Before the data can be entered into a database on the server, both the first-name and last-name fields must have a value. Lines 260–265 check that these fields are not empty and, if they’re empty, display the appropriate error message. Once all the data entered is valid, lines 266–275 hide the entry form and show the address book. Lines 273–274 call function callWebService
to invoke the addEntry
function of our web service with the data for the new contact. Once the server saves the data, it queries the database for an updated list of entries and returns them; then function parseData
displays the entries on the page.
• Despite the tremendous technological growth of the Internet over the past decade, the usability of web applications has lagged behind that of desktop applications.
• Rich Internet Applications (RIAs, p. 572) are web applications that approximate the look, feel and usability of desktop applications. RIAs have two key attributes—performance and rich GUI.
• RIA performance comes from Ajax (Asynchronous JavaScript and XML, p. 572), which uses client-side scripting to make web applications more responsive.
• Ajax applications separate client-side user interaction and server communication and run them in parallel, making the delays of server-side processing more transparent to the user.
• “Raw” Ajax (p. 572) uses JavaScript to send asynchronous requests to the server, then updates the page using the DOM.
• When writing “raw” Ajax you need to deal directly with cross-browser portability issues, making it impractical for developing large-scale applications.
• Portability issues are hidden by Ajax toolkits (p. 572), which provide powerful ready-to-use controls and functions that enrich web applications and simplify JavaScript coding by making it cross-browser compatible.
• We achieve rich GUI in RIAs with Ajax toolkits and with RIA environments such as Adobe’s Flex, Microsoft’s Silverlight and JavaServer Faces. Such toolkits and environments provide powerful ready-to-use controls and functions that enrich web applications.
• The client-side of Ajax applications is written in HTML5 and CSS3 and uses JavaScript to add functionality to the user interface.
• XML and JSON are used to structure the data passed between the server and the client.
• The Ajax component that manages interaction with the server is usually implemented with JavaScript’s XMLHttpRequest
object (p. 572)—commonly abbreviated as XHR.
• In traditional web applications, the user fills in the form’s fields, then submits the form. The browser generates a request to the server, which receives the request and processes it. The server generates and sends a response containing the exact page that the browser will render, which causes the browser to load the new page and temporarily makes the browser window blank. The client waits for the server to respond and reloads the entire page with the data from the response.
• While a synchronous request (p. 573) is being processed on the server, the user cannot interact with the client web browser.
• The synchronous model was originally designed for a web of hypertext documents—what some people call the “brochure web.” This model yielded “choppy” application performance.
• In an Ajax application, when the user interacts with a page, the client creates an XMLHttpRequest
object to manage a request. The XMLHttpRequest
object sends the request to and awaits the response from the server. The requests are asynchronous (p. 574), allowing the user to continue interacting with the application while the server processes the request concurrently. When the server responds, the XMLHttpRequest
object that issued the request invokes a callback function (p. 574), which typically uses partial page updates (p. 574) to display the returned data in the existing web page without reloading the entire page.
• The callback function updates only a designated part of the page. Such partial page updates help make web applications more responsive, making them feel more like desktop applications.
• A classic HTML5 registration form sends all of the data to be validated to the server when the user clicks the Register button. While the server is validating the data, the user cannot interact with the page. The server finds invalid data, generates a new page identifying the errors in the form and sends it back to the client—which renders the page in the browser. Once the user fixes the errors and clicks the Register button, the cycle repeats until no errors are found; then the data is stored on the server. The entire page reloads every time the user submits invalid data.
• Ajax-enabled forms are more interactive. Entries are validated dynamically as the user enters data into the fields. If a problem is found, the server sends an error message that’s asynchronously displayed to inform the user of the problem. Sending each entry asynchronously allows the user to address invalid entries quickly, rather than making edits and resubmitting the entire form repeatedly until all entries are valid. Asynchronous requests could also be used to fill some fields based on previous fields’ values.
• The term Ajax was coined by Jesse James Garrett of Adaptive Path in February 2005, when he was presenting the previously unnamed technology to a client.
• All of the technologies involved in Ajax (HTML5, JavaScript, CSS, dynamic HTML, the DOM and XML) had existed for many years before the term “Ajax” was coined.
• In 1998, Microsoft introduced the XMLHttpRequest
object to create and manage asynchronous requests and responses.
• Popular applications like Flickr, Google’s Gmail and Google Maps use the XMLHttpRequest
object to update pages dynamically.
• The name Ajax immediately caught on and brought attention to its component technologies. Ajax has quickly become one of the hottest technologies in web development, as it enables webtop applications to challenge the dominance of established desktop applications.
XMLHttpRequest
Object• The XMLHttpRequest
object (which resides on the client) is the layer between the client and the server that manages asynchronous requests in Ajax applications. This object is supported on most browsers, though they may implement it differently.
• To initiate an asynchronous request, you create an instance of the XMLHttpRequest
object, then use its open
method to set up the request and its send
method to initiate the request.
• When an Ajax application requests a file from a server, the browser typically caches that file. Subsequent requests for the same file can load it from the browser’s cache.
• For security purposes, the XMLHttpRequest
object does not allow a web application to request resources from servers other than the one that served the web application.
• Making a request to a different server is known as cross-site scripting (also known as XSS, p. 578). You can implement a server-side proxy—an application on the web application’s web server—that can make requests to other servers on the web application’s behalf.
• When the third argument to XMLHttpRequest
method open
is true
, the request is asynchronous.
• An exception (p. 581) is an indication of a problem that occurs during a program’s execution.
• Exception handling (p. 581) enables you to create applications that can resolve (or handle) exceptions—in some cases allowing a program to continue executing as if no problem had been encountered.
• A try
block (p. 581) encloses code that might cause an exception and code that should not execute if an exception occurs. A try
block consists of the keyword try
followed by a block of code enclosed in curly braces ({}
).
• When an exception occurs, a try
block terminates immediately and a catch
block (also called a catch
clause or exception handler, p. 581) catches (i.e., receives) and handles the exception.
• The catch
block begins with the keyword catch
(p. 581) and is followed by an exception parameter in parentheses and a block of code enclosed in curly braces.
• The exception parameter’s name enables the catch
block to interact with a caught exception object, which contains name
and message
properties.
• A callback function is registered as the event handler for the XMLHttpRequest
object’s readystatechange
event (p. 582). Whenever the request makes progress, the XMLHttpRequest
calls the readystatechange
event handler.
• Progress is monitored by the readyState
property, which has a value from 0 to 4. The value 0 indicates that the request is not initialized and the value 4 indicates that the request is complete.
• When passing structured data between the server and the client, Ajax applications often use XML because it consumes little bandwidth and is easy to parse.
• When the XMLHttpRequest
object receives XML data, the XMLHttpRequest
object parses and stores the data as a DOM object in the responseXML
property.
• The XMLHttpRequest
object’s responseXML
property contains the XML returned by the server.
• DOM method createElement
creates an HTML5 element of the specified type.
• DOM method setAttribute
adds or changes an attribute of an HTML5 element.
• DOM method appendChild
inserts one HTML5 element into another.
• The innerHTML
property of a DOM element can be used to obtain or change the HTML5 that’s displayed in a particular element.
• JSON (JavaScript Object Notation, p. 587)—a simple way to represent JavaScript objects as strings—is an alternative way (to XML) for passing data between the client and the server.
• Each JSON object is represented as a list of property names and values contained in curly braces.
• An array is represented in JSON with square brackets containing a comma-separated list of values.
• Each value in a JSON array can be a string, a number, a JSON representation of an object, true
, false
or null
.
• JavaScript’s JSON.parse
function can convert JSON strings into JavaScript objects.
• JSON strings are easier to create and parse than XML and require fewer bytes. For these reasons, JSON is commonly used to communicate in client/server interaction.
• To implement type-ahead (p. 588), you can use an element’s keyup
-event handler to make asynchronous requests.
16.1 Fill in the blanks in each of the following statements:
a. Ajax applications use ________ requests to create Rich Internet Applications.
b. In Ajax applications, the ________ object manages asynchronous interaction with the server.
c. The event handler called when the server responds is known as a(n) ________ function.
d. The ________ attribute can be accessed through the DOM to update an HTML5 element’s content without reloading the page.
e. JavaScript’s XMLHttpRequest
object is commonly abbreviated as ________.
f. ________ is a simple way to represent JavaScript objects as strings.
g. Making a request to a different server is known as ________.
h. JavaScript’s ________ function can convert JSON strings into JavaScript objects.
i. A(n) ________ encloses code that might cause an exception and code that should not execute if an exception occurs.
j. The XMLHttpRequest
object’s ________ contains the XML returned by the server.
16.2 State whether each of the following is true or false. If false, explain why.
a. Ajax applications must use XML for server responses.
b. The technologies that are used to develop Ajax applications have existed since the 1990s.
c. To handle an Ajax response, register for the XMLHttpRequest
object’s readystatechanged
event.
d. An Ajax application can be implemented so that it never needs to reload the page on which it runs.
e. The responseXML
property of the XMLHttpRequest
object stores the server’s response as a raw XML string.
f. An exception indicates successful completion of a program’s execution.
g. When the third argument to XMLHttpRequest
method open
is false
, the request is asynchronous.
h. For security purposes, the XMLHttpRequest
object does not allow a web application to request resources from servers other than the one that served the web application.
i. The innerHTML
property of a DOM element can be used to obtain or change the HTML5 that’s displayed in a particular element.
16.1
a. asynchronous.
b. XMLHttpRequest
.
c. callback.
d. innerHTML
.
e. XHR.
f. JSON.
g. cross-site scripting (or XSS).
h. JSON.parse
.
i. try
block.
j. responseXML
property.
16.2
a. False. Ajax applications can use any type of textual data as a response. For example, we used JSON in this chapter.
b. True.
c. True.
d. True.
e. False. If the response data has XML format, the XMLHttpRequest
object parses it and stores it in a document object.
f. False. An exception is an indication of a problem that occurs during a program’s execution.
g. False. The third argument to XMLHttpRequest
method open
must be true
to make an asynchronous request.
h. True.
i. True.
16.3 Describe the differences between client/server interactions in traditional web applications and client/server interactions in Ajax web applications.
16.4 Consider the AddressBook
application in Fig. 16.9. Describe how you could reimplement the type-ahead capability so that it could perform the search using data previously downloaded rather than making an asynchronous request to the server after every keystroke.
16.5 Describe each of the following terms in the context of Ajax:
a. type-ahead
b. edit-in-place
d. asynchronous request
e. XMLHttpRequest
f. “raw” Ajax
g. callback function
h. same origin policy
i. Ajax libraries
j. RIA
[Note to Instructors and Students: Owing to security restrictions on using XMLHttpRequest
, Ajax applications must be placed on a web server (even one on your local computer) to enable them to work correctly, and when they need to access other resources, those must reside on the same web server. Students: You’ll need to work closely with your instructors in order to understand your lab setup, so that you can run your solutions to the exercises (the examples are already posted on our web server), and in order to run many of the other server-side applications that you’ll learn later in the book.]
16.6 The XML files used in the book-cover catalog example (Fig. 16.8) also store the titles of the books in a title
attribute of each cover
node. Modify the example so that every time the mouse hovers over an image, the book’s title is displayed below the image.
16.7 Create an Ajax-enabled version of the feedback form from Fig. 2.15. As the user moves between form fields, ensure that each field is nonempty. For the e-mail field, ensure that the e-mail address has a valid format. In addition, create an XML file that contains a list of e-mail addresses that are not allowed to post feedback. Each time the user enters an e-mail address, check whether it’s on that list; if so, display an appropriate message.
16.8 Create an Ajax-based product catalog that obtains its data from JSON files located on the server. The data should be separated into four JSON files. The first should be a summary file, containing a list of products. Each product should have a title, an image filename for a thumbnail image and a price. The second file should contain a list of descriptions for each product. The third file should contain a list of filenames for the full-size product images. The last file should contain a list of the thumbnail-image file names. Each item in a catalogue should have a unique ID that should be included with the entries for that product in every file. Next, create an Ajax-enabled web page that displays the product information in a table. The catalog should initially display a list of product names with their associated thumbnail images and prices. When the mouse hovers over a thumbnail image, the larger product image should be displayed. When the user moves the mouse away from that image, the original thumbnail should be redisplayed. You should provide a button that the user can click to display the product description.
18.220.16.184