Search the Web:

Google

Monday, January 7, 2008

Ajaxifying a Web Application - Auto-population of Data

To start, let’s take a look at the source code for a form that a customer must fill in:

<html>
<head>
<title>Customer Data Screen</title>
</head>
<body>
<h1>Corporate System</h1>
<h2>Enter Customer Data</h2>
<table>
<tr>
<th>Customer Name:</th>
<td><input type="text" name="name"/></td>
</tr>
<tr>
<th>Address:</th>
<td>
<input type="text" name="address"/>
</td>
</tr>
<tr><th
>City:</th><td>
<input type="text" name="city"/>
</td>
</tr>
<tr><th>State:</th><td>
<input type="text" name="state"/>
</td>
</tr>
<tr>
<th>Zip:</th><td>
<input type="text" name="zip"/>
</td></tr>
<tr>
<th></th>
<td><input type="Submit" value="Add Customer"/>
</td>
</tr>
</table>
</body>
</html>

We want to add behavior so that when the user enters a value in the Zip field, we’ll send the ZIP code to the server, receive a response containing the city and state that correspond to the ZIP, and populate the City and State fields with those values.

Preparing the HTML
The first step towards this end will be to add an event handler to the event handler Zip <input> tag. Event Handlers in HTML allow you to execute script code in the web page when certain user interactivity or browser tasks occur. Secondly, we’ll have to add id= attributes to the City and State <input> elements. Our revised <input> elements look like this (with the surrounding table rows shown for context):

<tr>
<th>Zip:</th><td><input onblur="getZipData(this.value)"type="text" name="zip"/>
</td>
</tr>
<tr>
<th>City:</th><td><input id="city" type="text" name="city"/>
</td>
</tr>
<tr>
<th>State:</th><td><input id="state" type="text" name="state"/>
</td>
</tr>

The event handler is registered via the onblur= attribute, which in this case specifies that the script function named getZipData( ) will be invoked when the focus leaves this element. The parameter passed to this function, this.value, specifies that the value property of the <input> element will be passed; the this is a reference to the element on which the event handler has been registered.
We’ve also changed the ordering of the table rows; now the Zip input comes first. While this new layout is atypical for American addresses, it reflects a more natural flow for the ajaxified version of the screen, since entering the ZIP code will auto-populate the other two fields beneath it.

Communicating with the Server

We’re now done with the first half of our task: wiring the HTML to a script that will perform our Ajax behavior. Now we need to tackle the slightly trickier second bit: writing the script. The key to Ajax is a JavaScript object called XMLHttpRequest, the engine that can send HTTP requests, receive responses, and parse them as XML. Let’s create our getZipData( ) function, which will create an instance of XMLHttpRequest and use it to send the ZIP code to the server. Remember, this function will be invoked whenever the Zip input loses focus; that is, whenever the user enters the field and then leaves it, either with the mouse, the tab key, or some other mechanism. Here’s what it looks like so far:

1 <script type="text/JavaScript">
2 var xhr;
3 function getZipData(zipCode) {
4 xhr = new XMLHttpRequest();
5 xhr.open("GET", "/getCityStateFromZip.request?" + zipCode);
6 xhr.send(null);
7 }
8 </script>

On line 4, we create our XMLHttpRequest instance. On the next line, we configure it using the open( ) function; the first parameter indicates the HTTP method to use for the request, and the second indicates the URL we’ll be requesting. Finally, we invoke the send( ) function, which predictably enough sends the request.

Parsing the Response

Now that we’ve demonstrated how to send a request to the server, we need to add some code that will process the response that the server sends back. We’ll do that by creating a function processZipData( ):

1 function processZipData() {
2 var data = xhr.responseText;
3 var cityState = data.split(',');
4 document.getElementById("city").value = cityState[0];
5 document.getElementById("state").value = cityState[1];
6 }


The first few lines of this function are fairly intuitive; we retrieve the data sent back fromthe server—the city and state, formatted as “City,State”— and split the string into a two-element string array, so that we can access the city and state values separately. Lines 4 and 5 demonstrate why we gave id attributes to the City and State input elements earlier. Web browsers model every web page they display as an XML document (regardless of how ugly the page’s HTML markup is). In JavaScript code, we can access this XML document using the document variable. document has a handy getElementById( ) function that can return a reference to any XML element based on the id attribute. Once we have a reference to the element, we can manipulate it. In this case, we set the value attribute of the elements to the city and state values returned by the server.

Tying It All Together

We’ve created two JavaScript functions: getZipData( ), which sends a request to the server, and processZipData( ), which processes the response. However, we haven’t yet connected them. As our code currently stands, processZipData will never be invoked.


You might think that we should invoke processZipData( ) as we do on line 5 of the following example.

1 function getZipData(zipCode) {
2 xhr = new XMLHttpRequest();
3 xhr.open("GET", "/getCityStateFromZip.request?" + zipCode);
4 xhr.send(null);
5 processZipData();
6 }

Unfortunately, this just doesn’t work. The “A” in Ajax stands for asynchronous, and asynchronous behavior is exactly what we’re seeing here.

It turns out that when we invoke the send function on line 4, the invocation returns immediately and the XMLHttpRequest will make the request and receive the response on a separate thread. Thus, if we were to try to process the response from the server on the following line, we
couldn’t—we would not yet have received the response.
The solution is to register a callback handler—a function that will be callback handler
invoked when the XMLHttpRequest has received the response from the server. Line 3 in the following example demonstrates how to register processZipData as a callback handler:
1 function getZipData(zipCode) {
2 xhr = new XMLHttpRequest();
3 xhr.onreadystatechange=processZipData;
4 xhr.open("GET", "/getCityStateFromZip.request?" + zipCode);
5 xhr.send(null);
6 }


By simply passing the name of the function to the onreadystatechange( ) method, we are almost ready. Why is the method named onreadystatechange( ) and not, say, onresponsereceived( )? It turns out that XMLHttpRequest calls back into the function we registered multiple times as
it sends the request and receives the response, each time indicating that it has made progress. We’re only interested in parsing the data once the entire process has finished, so we need to check the current status of the XMLHttpRequest before we attempt to get the response data
in processZipData( ):
1 function processZipData() {
2 if (xhr.readyState == 4) {
3 var data = xhr.responseText;
4 var cityState = data.split(',');
5 document.getElementById("city").value = cityState[0];
6 document.getElementById("state").value = cityState[1];
7 }
8 }

XMLHttpRequest provides a readyState property that indicates its current status; a state of “4” indicates that the response has been received.


That’s it, we’re done. Let’s take a look at the entire web page source code to see how all these pieces fit together:
<html>
<head>
<title>Customer Data Screen</title>
<script type="text/javascript">var xhr;
function getZipData(zipCode) {
xhr = new XMLHttpRequest();
//<label id="code.xhr"/>
xhr.onreadystatechange=processZipData;xhr.open"GET","/getCityStateFromZip.request?" + zipCode);xhr.send(null);
}
function processZipData() {
if (xhr.readyState == 4) {
var data = xhr.responseText;
var cityState = data.split(',');
document.getElementById("city").value = cityState[0];
document.getElementById("state").value = cityState[1];
}
}
</script>
</head>
<body>
<h1>Corporate System
</h1><h2>Enter Customer Data</h2><table><tr><th>Customer Name:</th><td><input type="text" name="name"/>
</td></tr><tr>
<th>Address:</th>
<td><input type="text" name="address"/>
</td></tr><tr><th>Zip:</th><td>
<input onblur="getZipData(this.value)"type="text" name="zip"/>
</td>
</tr>
<tr>
<th>City:
</th>
<td>
<input id="city" type="text" name="city"/>
</td>
</tr>
<tr>
<th>State:</th>
<td>
<input id="state" type="text" name="state"/>
</td>
</tr>
<tr>
<th>
</th>Report
<td><input type="Submit" value="Add Customer"/>
</td>
</tr>
</table>
</body>
</html>

No comments: