Last Updated:

Ultra-dynamic web interfaces

One of the main difficulties faced by developers of web application interfaces is that after the page is in the client browser, the browser's connection with the server ends. Any action with an interface element requires a second call to the server and a new page is reloaded. Because of this, the web application loses its elegance and runs slowly. In this article, I will discuss how this problem can be solved by using JavaScript and the XMLHttpRequest object.

I'm sure you're familiar with the traditional web application front-end model. The user requests a page from the server, which is created on the server and then forwarded to the browser. This page has HTML elements that describe the form into which the user enters data. After that, the user sends the data to the server and receives a new page based on the entered data, and the process is repeated. This whole process is determined by the very nature of the HTTP protocol and is different from how we work with conventional applications, the interface of which is inextricably linked with the program logic.

Let's take a simple example of entering a serial number in a Windows application. According to the rules, after you finish typing an intricate set of numbers and letters into the fields, a green "tick" will appear next to them, indicating that you have entered the correct number. It appears instantly, as a result of the logic "sewn" into the interface. As soon as you have finished dialing the number, the program checks it and gives a response.

In the web interface, this standard behavior looks completely different. Of course, the fields in which you enter the serial number look exactly the same, but when the input is complete, the user needs to press the button to send a page to the server that will check the entered data. A new page will be returned back to the user, displaying a message about the correct or incorrect serial number. If the user fails, they should return to the previous page and try again. And so on ad infinitum.

Of course, it is not often that a web application requires the user to enter a serial number, but there are countless other examples where a quick response of the interface to the user's actions would be very useful. And since all the software logic is on the server, it is very difficult to get such a result in a traditional web application.

JavaScript appears on the scene

Thanks to JavaScript, a certain amount of programming logic can be transferred to an HTML page, which will allow you to quickly respond to user actions. However, this solution has one main drawback. The first problem is that once JavaScript enters the user's browser along with the page, the programming logic is available for viewing with the naked eye. In the case of, for example, checking the correctness of the entered e-mail address, this may not be terrible, but if the check is associated with a serial number, the verification algorithm becomes available to everyone who downloaded the page, and this is unacceptable.

The second problem is that serious programming logic cannot be put into a page, since the interface is simply not designed for this. All the logic should be at the application level, not the user interface, which means that we return to the server again. The problem is compounded by the fact that it is not always certain that JavaScript is present in the client's browser. While most users leave JavaScript support enabled in their browsers, there are a significant number of users who don't, or use a browser where JavaScript is missing or not needed as a class at all. Therefore, all the logic that JavaScript does on the client side will still have to be checked on the server just in case.

XMLHttpRequest object

The solution to this problem may be the XMLHttpRequest object. This object was first implemented by Microsoft as an ActiveX object, but it is now available as an embedded object in all Mozilla and Safari browsers. This object allows JavaScript to make HTTP requests to a remote server without having to reload the page. In fact, HTTP requests are sent and received completely behind the scenes of the page, and the user does not even notice them.

This is a huge step forward, as it allows the developer of the web application to achieve the most desirable goal - the creation of a fast user interface while maintaining the program logic on the server. With the help of JavaScript, the data entered by the user is sent to the server, where they are processed and the user almost immediately receives a response to the entered data.

Let's start with the basics

Because of its controversial history, the XMLHttpRequest object is not yet part of any standard (although something similar has already been proposed in the W3C DOM Level 3 Load and Save specification). Therefore, there are two different methods for calling this object in script code. In Internet Explorer, the ActiveX object is called as follows:

var req = new ActiveXObject("Microsoft.XMLHTTP");

In Mozilla and Safari, this is made easier (since there it is an object built into JavaScript):

var req = new XMLHttpRequest();

Of course, because of such differences, you need to create branches in your code, each of which will be executed depending on which browser the script is loaded in. There are several ways to do this (including various sophisticated hacks and the method of "conditional comments"). But I think it's best to just check in your code to see if a particular object is supported by the browser. A good example is the code taken from the Apple website, where the documentation for this object is posted. Let's use it:

var req;

function loadXMLDoc(url) {
    // branch for native XMLHttpRequest object
    if (window.XMLHttpRequest) {
        req = new XMLHttpRequest();
        req.onreadystatechange = processReqChange;
        req.open("GET", url, true);
        req.send(null);
    // branch for IE/Windows ActiveX version
    } else if (window.ActiveXObject) {
        req = new ActiveXObject("Microsoft.XMLHTTP");
        if (req) {
            req.onreadystatechange = processReqChange;
            req.open("GET", url, true);
            req.send();
        }
    }
}

In this code, it is especially important to pay attention to the onreadystatechange property. See how it is assigned the value of the processReqChange function. This property is an event handler that is triggered whenever the state of the req object changes. States are indicated by numbers 0 (the object is not initialized) through 4 (request completed). This is important because our script will not wait for a response from the server to continue its work. The HTTP request will be generated and sent to the server, but the script will be executed further. Because we chose this behavior, we can't just return the result of the query at the end of the function, because we don't know if we've received it by that time or not. To do this, we have provided the processReqChange function, which will monitor the state of the req object, and will inform us at the right time that the process of obtaining the document is over, and we can go further.

To do this, the processReqChange function needs to test two things. The first is to wait for the state of the req object to change to 4 (meaning that the process of retrieving the document from the server is over). The second is to check the HTTP status of the response. You know that code 404 means "file not found" and 500 means "an error occurred on the server". But we need good old 200 code ("all OK"), which means that our request was successfully executed on the server. If we have received both state 4 and code 200, we can continue to execute our script and process the results received from the server. Of course, otherwise we have to handle all errors, for example, if the response code is different from 200.

function processReqChange() 
{
    // only if req shows "complete"
    if (req.readyState == 4) {
        // only if "OK"
        if (req.status == 200) {
            // ...processing statements go here...
        } else {
            alert("There was a problem retrieving 
               the XML data:\n" + req.statusText);
        }
    }
}

In practice

I'm going to create a working example to demonstrate the above ideas. Many web applications have a procedure where a new user registers on the server and you need to select a "nickname" to register. Very often, this "nickname" must be unique, and therefore after the user has chosen a "nickname", the server checks against the user database, whether there is already such a "nickname" or not. If you've ever signed up for a webmail server, you'll remember how tedious it is to find a nickname that no one else uses. It would be very nice if the check was carried out without having to refresh the page every time.

To solve the problem, we'll use four key elements: an XHTML form, a JavaScript function specifically written for this situation, two of our common functions, which we talked about above, and finally a server-side script that will access the database.

Form

This is the easiest part of the job - a simple form with a field for entering a "nickname". We bind our validation script to the onblur event. In order to display a message to the user about the results of the check, I inserted it into the form and hid it using CSS. This is a more elegant and polite way than the standard alert() dialog box.

<input id="username" name="username" type="text" 
  onblur="checkName(this.value,'')" />
<span class="hidden" id="nameCheckFailed">
  This name is in use, please try another. 
</span>

CSS declares the properties of the hidden class, as well as another error class that displays error messages.

span.hidden{
  display: none;
}

span.error{
  display: inline;
  color: black;
  background-color: pink;  
}

Processing of entered data

Use the checkName function to validate the data entered by the form user. The task of the function is to take the data, decide which server script to give this data to, call another function that will do all the dirty work with HTTP requests and responses, and then process the response. This function of ours will consist of two parts. One part takes the data from the form and the other part processes the response from the server. I'll explain why this is done in a moment.

function checkName(input, response)
{
  if (response != ''){ 
    // Response mode
    message   = document.getElementById('nameCheckFailed');
    if (response == '1'){
      message.className = 'error';
    }else{
      message.className = 'hidden';
    } 
  }else{
    // Input mode
    url  = 'http://localhost/xml/checkUserName.php?q=' \\
    + input;
    loadXMLDoc(url);
  }

}

The response is processed simply - the response we will receive from the server script will be either 1 or 0. 1 means that such a "nickname" is already being used by someone. Depending on the response, our function changes the class name of the error message - it is either shown or hidden. As you can see from the code, the checkUserName.php script performs work on the server.

Generating an HTTP Request and Response

As you saw above, working with HTTP is done by two functions: loadXMLDoc and processReqChange. In the first, almost nothing needs to be changed, and in the second, something needs to be changed to work with the DOM.

As you will recall, until the successful result of the query is passed to the processReqChange function, we cannot return any value from this function. Because of this, we need to make an explicit call to the function from elsewhere in the code to take advantage of the result. This is why the checkName function is divided into two parts. Therefore, the main task of the processReqChange function is to process the XML response received from the server and pass a specific value from this response to the checkName function.

We do not want to introduce any specific code into these functions, as these functions can be used by other elements of the page to access the server. That's why we didn't put the name of the checkName function in the processReqChange function. Instead, we decided that the server itself would give the name of the function that accessed it in its response.

<?xml version="1.0" encoding="UTF-8" 
  standalone="yes"?>
<response>
  <method>checkName</method>
  <result>1</result>
</response>

Parsing this simple answer is performed without any problems.

function processReqChange() 
{
    // only if req shows "complete"
    if (req.readyState == 4) {
        // only if "OK"
        if (req.status == 200) {
            // ...processing statements go here...
      response = req.responseXML.documentElement;

      method = response.getElementsByTagName('method') \\
            [0].firstChild.data;

      result = response.getElementsByTagName('result') \\
            [0].firstChild.data;

      eval(method + '(\'\', result)');
        } else {
            alert("There was a problem retrieving \\
            the XML data:\n" + req.statusText);
        }
    }
}

By accessing the responseXML property of the XMLHttpRequest object, we get an XML response that comes from the server, which we then parse using the DOM. By taking the name of the function that required this response from the response, we know which function to pass the resulting value to. After you finish testing, remove the condition else so that the script does not bother users with an extra error message.

Server Script

The last piece of our puzzle is a server-side script that will receive the request, process it, and return the response as XML. In our example, the script will access the user database to determine whether a given nickname is already in use or not. For brevity in my example, the script does not use a database, but simply checks the two names "Drew" and "Fred".

<?php
header('Content-Type: text/xml');

function nameInUse($q)
{  
  if (isset($q)){
    switch(strtolower($q))
    {
      case  'drew' :
          return '1';
          break;
      case  'fred' :
          return '1';
          break;
      default:
          return '0';
    }
  }else{
    return '0';
  }
  
}
?>

<?php echo '<?xml version="1.0" encoding="UTF-8"
  standalone="yes"?>'; ?>
<response>
  <method>checkName</method>
  <result><?php 
    echo nameInUse($_GET['q']) ?>
  </result>
</response>

Of course, the same verification procedure should be provided for in the server script that will be executed when the user clicks the "Submit" button on the registration form. This is in case JavaScript was disabled in his browser, or the user intentionally entered a "nickname" that is already in use. In addition, on loaded sites, it may happen that at the time of choosing the "nickname" it was free, and at the time of submission it was already occupied by someone.

As a next step, extend the functionality of the script itself. For example, the server may return alternate nicknames based on a nickname selected by the user but already occupied.

In conclusion

This small example is only lightly concerned with the topic of using the XMLHttpRequest object. Here are just some examples of its use: Google Suggest, where XMLHttpRequest is used to suggest search words, the Ta-da Lists application, where data is fed to the server again using XMLHttpRequest so that the interface works very quickly. The most interesting thing here is not to make the code work, but to find any other interesting ways to use it.