Article

AJAX: Usable Interactivity with Remote Scripting

Page: 1 2 3 Next

Example 1: Implement XMLHttpRequest

In a traditional server/client application, the entire ecard form would have to be submitted to the server, checked, and returned to the browser before the client could be made aware of whether their receipt number was valid or not. With the remote scripting model, we're able to check the receipt number as soon as the user has finished dealing with that field. So, when a user submits the form, the browser has already identified whether or not the data is valid.

The first step in checking the data remotely is to know when the user has entered a value into the receipt number field. This can be detected using an onchange event handler for the field. A "change" on a text field is registered whenever the user modifies the value of the text field and then "blurs" away from that field (i.e. they tab or click away from it). This is normally a good indication that a user has finished filling out the field, and that the data it contains can be processed. By capturing this onchange event, we can tell our script to begin validating the field's content:

receipt.onchange = onchangeReceipt;

onchangeReceipt is a function that is called when the onchange event is triggered. It's inside this function that we initialise our XMLHttpRequest object and send off the relevant data to be checked:

var requester = null;  
 
function onchangeReceipt()  
{  
 /* Check for running connections */  
 if (requester != null && requester.readyState != 0 && requester.readyState != 4)  
 {  
   requester.abort();  
 }  
 
 try  
 {  
   requester = new XMLHttpRequest();  
 }  
 catch (error)  
 {  
   try  
   {  
     requester = new ActiveXObject("Microsoft.XMLHTTP");  
   }  
   catch (error)  
   {  
     requester = null;  
 
     return false;  
   }  
 }  
 
 requester.onreadystatechange = requesterExecuteAction;  
 
 requester.open("GET", "receipt.php?receipt=" + this.value);  
 requester.send(null);  
 
 return true;  
}

You might recognise some of that syntax from the first part of this article, namely the forked try/catch structure, and the open() and send() methods that control the XMLHttpRequest object.

The first if statement checks to see whether or not an XMLHttpRequest object already exists and is currently running; if so, it aborts that connection. This ensures that a number of conflicting XMLHttpRequest calls aren't run simultaneously, which would clog up the network. The function then continues on, to create a new XMLHttpRequest object and open a connection to the server-side validation script, receipt.php.

In receipt.php, the CGI variable receipt is checked and, if its value is "1234567", some XML data is returned; otherwise, a plain text string of "empty" is returned, indicating that the receipt number is invalid:

if ($receipt == "1234567")  
{  
 header("Content-type: text/xml");  
 
 $filePointer = fopen("example.xml", "r");  
 $exampleXML = fread($filePointer, filesize("example.xml"));  
 fclose($filePointer);  
 
 print($exampleXML);  
}  
else  
{  
 header("Content-type: text/plain");  
 print("empty");  
}

Hard-coded values and data have been used in this example to simplify the code, but in the real world, this PHP script would check the receipt number against a database, and return the appropriate data for that number.

Note that if receipt number is invalid, the content-type header sent is "text/plain". This simplifies the message printing process somewhat, but it also means that, on the client side, the responseXML property of the XMLHttpRequest object will not contain anything. As such, you should always be aware of what your server-side scripts return, and keep an eye on responseXML or responseText appropriately.

As well as calling the server-side script, onchangeReceipt() also assigns onreadystatechangeReceipt() to monitor the status of the connection via the onreadystatechange event, and it is this function that determines when the connection is finished and further action should be taken. To do this, we use the previously discussed readyState/status condition nesting:

function onreadystatechangeReceipt()  
{  
 /* If XMLHR object has finished retrieving the data */  
 if (requester.readyState == 4)  
 {  
   /* If the data was retrieved successfully */  
   if (requester.status == 200)  
   {  
     writeDetails();  
   }  
   /* IE returns a status code of 0 on some occasions, so ignore this case */  
   else if (requester.status != 0)  
   {  
     alert("There was an error while retrieving the URL: " + requester.statusText);  
   }  
 }  
 
 return true;  
}

When a successful status code is returned, writeDetails() is invoked. It is this function that parses the returned data and determines what to do to the Web page:

function writeDetails()  
{  
 var receipt = document.getElementById("receipt");  
 
 if (requester.responseText.charAt(0) == "<")  
 {  
   var email = document.getElementById("email");  
   var name = document.getElementById("name");  
 
   receipt.valid = true;  
   email.value = requester.responseXML.getElementsByTagName("email")[0].  
childNodes[0].nodeValue;  
 }  
 else  
 {  
   receipt.valid = false;  
 }  
 
 return true;  
}

This function firstly checks the responseText property of the XMLHttpRequest object, to see whether the receipt number was valid or not. If it is valid, the data will be in XML format and its first character will be an opening angled bracket (<); otherwise, it will be a plain string. In each case, the extended property valid is set appropriately on the receipt number field. Additionally, if the receipt number is valid, extra data is added to the email field, having been parsed from the responseXML property of the XMLHttpRequest object.

The execution of writeDetails() marks the end of the remote scripting process for receipt number validation. With the extended valid property set on the field, the browser knows whether or not the data is OK, and can alert users of any errors when they try to submit the form:

orderForm.onsubmit = checkForm;  
 
function checkForm()  
{  
if (!receipt.valid)  
{  
 receipt.focus();  
 alert("Please enter a valid receipt number.");  
 
 return false;  
}  
 
...

If there is an error with the form, an alert() dialog appears when the submit button is clicked, asking the user to correct the error before the form is submitted:

1482_figure2

checkForm() also handles the submission of the form data via remote scripting (though, in reality, normal form submission would probably suffice for an application like this). The remote scripting for the data submission uses the same code we used for validation, but a different server-side script is supplied to process the data, and instead of onreadystatechangeReceipt() being called once the connection has finished, onreadystatechangeForm() is called.

onreadystatechangeForm() triggers sentForm() to re-write the Web page and inform the user that the ecard was either successfully or unsuccessfully sent, depending upon the data returned from the server:

function sentForm()  
{  
 var body = document.getElementsByTagName("body")[0];  
 
 body.innerHTML = "<h1>Send someone an e-card from ExampleCo!</h1>";  
 
 if (formRequester.responseText == "success")  
 {  
   body.innerHTML += "<h1>Send someone an e-card from ExampleCo!</h1><p>Your ExampleCo e-card has been sent!</p>";  
 }  
 else  
 {  
   body.innerHTML += "<p>There was an error while sending your ExampleCo e-card.</p>";  
 }  
 
 return true;  
}

This removes the initial form presented to the user, and inserts a final status message:

1482_figure3

While this application re-writes almost the whole page, it's easy to see how specific parts of the DOM could be changed using remote scripting, which would enable separate parts of an application interface to update independently of the Web page itself.

If you liked this article, share the love:
Print-Friendly Version Suggest an Article

Sponsored Links