Ajax Submission Throttling

Since Ajax emphasizes avoiding page refreshes, the question of when to send user data is important. In a traditional web site or web application, each click makes a request back to the server so that the server is always aware of what the client is doing. In the Ajax model, the user interacts with the site or application without additional requests being generated for each click.

One solution would be to send data back to the server every time a user action occurs, similar to that of a traditional web solution. Thus, when the user types a letter, that letter is sent to the server immediately. The process is then repeated for each letter typed. The problem with this approach is that it has the possibility to create a large number of requests in a short amount of time, which may not only cause problems for the server but may cause the user interface to slow down as each request is being made and processed. The Submission Throttling design pattern is an alternative approach to this problematic issue.

Using Submission Throttling, you buffer the data to be sent to the server on the client and then send the data at predetermined times. The venerable Google Suggest feature does this brilliantly. It doesn't send a request after each character is typed. Instead, it waits for a certain amount of time and sends all the text currently in the text box. The delay from typing to sending has been fine-tuned to the point that it doesn't seem like much of a delay at all. Submission Throttling, in part, gives Google Suggest its speed.

Submission Throttling typically begins either when the web site or application first loads or because of a specific user action. Then, a client-side function is called to begin the buffering of data. Every so often, the user's status is checked to see if he or she is idle (doing so prevents any interference with the user interface). If the user is still active, data continues to be collected. When the user is idle, which is to say he or she is not performing an action, it's time to decide whether to send the data. This determination varies depending on your use case; you may want to send data only when it reaches a certain size, or you may want to send it every time the user is idle. After the data is sent, the application typically continues to gather data until either a server response or some user action signals to stop the data collection. Figure 1 outlines this process.

Figure 1

The Submission Throttling pattern should never be used for mission-critical data. If information must be posted to the server within a specific amount of time, you are better off using a traditional form to ensure the correct and timely delivery of the information.

Incremental Form Validation Example

As mentioned previously, Submission Throttling can be achieved through various user interactions. When using forms, it's sometimes useful to upload data incrementally as the form is being filled out. The most common usage is to validate data as the user is filling in the form instead of waiting until the end to determine any errors. In this case, you would most likely use the onchange event handler of each form element to determine when to upload the data.

The change event fires for a <select/> element whenever a different option is selected; it fires for other controls when its value has changed and it has lost focus. For example, if you typed a couple of letters into a text box and then clicked elsewhere on the screen (causing the text box to lose focus), the change event fires and the onchange event handler is called. If you click in the text box again, and then click elsewhere (or press the Tab key), the text box will lose focus but the change event will not fire because no changes have been made. Using this event handler for Submission Throttling can prevent extraneous requests.

Normally, the form validation is simply a precursor to submission. The form's submit button starts out disabled, becoming enabled only when all fields in the form have been validated by the server. For example, suppose you are running a web site where users must sign up to gain access to certain features. This may be a shopping site that requires sign-in to purchase items or a site that requires membership to access the message board. The items you'll want to be sure of when creating this new account are:

  • The user name must not be taken.
  • The e-mail address must be valid.
  • The birthday must be a valid date.

Of course, the type of data required will differ depending on your usage, but these items provide a good starting point for most applications.

Ajax Submission Throttling

Defining the HTML Form - No Ajax Required

The first step in creating such interaction is to define the HTML form that will collect the data. This form should stand alone so that it can be used even if Ajax calls aren't possible:

<form method="post" action="Success.php">
    <table>
        <tr>
            <td><label for="txtFirstName">First Name</label></td>
            <td><input type="text" id="txtFirstName" 
                       name="txtFirstName" /></td>
        </tr>
        <tr>
            <td><label for="txtLastName">Last Name</label></td>
            <td><input type="text" id="txtLastName"
                       name="txtLastName" /></td>
        </tr>
        <tr>
            <td><label for="txtEmail">E-mail</label></td>
            <td><input type="text" id="txtEmail" name="txtEmail" />
        <img src="error.gif" alt="Error" id="imgEmailError"
        style="display:none" /></td>
        </tr>
        <tr>
            <td><label for="txtUsername">Username</label></td>
            <td><input type="text" id="txtUsername"
                       name="txtUsername" />
        <img src="error.gif" alt="Error" id="imgUsernameError"
        style="display:none" /></td>
        </tr>
        <tr>
            <td><label for="txtBirthday">Birthday</label></td>
            <td><input type="text" id="txtBirthday"
                       name="txtBirthday" />
        <img src="error.gif" alt="Error" id="imgBirthdayError" 
        style="display:none" /> (m/d/yyyy)</td>
        </tr>
        <tr>
            <td><label for="selGender">Gender</label></td>
            <td><select id="selGender" name="selGender">
        <option>Male</option></option>Female
        </option></select></td>
        </tr>
    </table>
    <input type="submit" id="btnSignUp" value="Sign Up!" />
</form>

You should note a few things about this form. First, not all fields will be validated using Ajax calls. The fields for first and last name as well as gender (represented by a combo box) don't require validation. The other fields — for e-mail, user name, and birthday — will make use of Ajax validation. Second, you'll note that these fields have a hidden image after the text box. This image is used only in the event that there is a validation error. Initially the images are hidden, because those browsers without Ajax capabilities should never see them. There is absolutely no JavaScript on this form; all the appropriate functions and event handlers are defined in a separate file.

A single function called validateField() is used to validate each form field. This is possible because each field uses the same validation technique (call the server and wait for a response). The only differences are the types of data being validated and which image to show if validation is unsuccessful.

Ajax Submission Throttling

Building the Server-side Validation

The server-side functionality is stored in a file named ValidateForm.php. This file expects a name-value pair to be passed in the query string. The name should be the name of the control whose value is being checked, and the value should be the value of that control. Depending on the name of the control, this page runs the appropriate validation tests on the value. Then, it outputs a simple string in the following format:

<true|false>||<error message>

The first part of this string indicates whether the value is valid (true if it is; false if not). The second part, after the double pipes (||), is an error message that is provided only when the value is invalid. Here are a couple of examples of what the returned string might look like:

true||
false||Invalid date.

The first line represents a valid value; the second represents an invalid date. The code that does the validation is as follows:

<?php
    $valid = "false";
    $message = "An unknown error occurred.";

    if (isset($_GET["txtUsername"])) {
    
        //load array of usernames
        $usernames = array();
        $usernames[] = "SuperBlue";
        $usernames[] = "Ninja123";
        $usernames[] = "Daisy1724";
        $usernames[] = "NatPack";
        
        //check usernames
        if (in_array($_GET["txtUsername"], $usernames)) {
            $message = "This username already exists.
                        Please choose another.";
        } else if (strlen($_GET["txtUsername"]) < 8) {
            $message = "Username must be at least 8 characters long.";
        } else {
            $valid = "true";
            $message = "";
        }
    
    } else if (isset($_GET["txtBirthday"])) {
    
        $date = strtotime($_GET["txtBirthday"]);
        if ($date < 0) {
            $message = "This is not a valid date.";
        } else {
            $valid = "true";
            $message = "";
        }
    
    } else if (isset($_GET["txtEmail"])) {
    
        if(!eregi(
           "^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+
           (\.[a-z0-9-]+)*(\.[a-z]{2,3})$",
           $_GET["txtEmail"])) {
            $message = "This e-mail address is not valid";
        } else {
            $valid = "true";
            $message = "";
        }    
    }

    echo "$valid||$message"; ?>

In this file, the first step is to determine which field to validate. This is done using the isset() function to test the $_GET array for a value. If there is a value for a particular field, then the validation commences. For the user name, the value is checked to see if it already exists in an array of user names and then checked to ensure that it is at least eight characters long. The birthday is passed directly into PHP's built-in strtotime() function, which converts a date string in any number of U.S. formats into a UNIX timestamp (the number of seconds since January 1, 1970). If there is an error, this function returns -1, indicating that the string passed in was not a valid date. The e-mail address is checked against a regular expression to ensure that it is in the correct format. This regular expression was devised by John Coggeshall in his article, "E-mail validation with PHP 4," available online at www.zend.com/zend/spotlight/ev12apr.php.

Note that the user names in this example are stored in a simple array and hard-coded into the page. In an actual implementation, the user names should be stored in a database and the database should be queried to determine whether the user name already exists.

The $valid and $message variables are initialized to false and An unknown error occurred. This ensures that if the file is used incorrectly (passing in an unrecognized field name, for example), a negative validation will always be returned. When a positive validation occurs, however, this requires that both variables be reset to appropriate values (true for $valid, an empty string for $message). In the case of a negative validation, only the $message variable has to be set since $valid is already false. The very last step in this page is to output the response string in the format mentioned previously.

Ajax Submission Throttling

Performing the Validation with JavaScript

Next, the JavaScript to perform the validation must be created. A single function, validateField(), can be used to validate each field so long as it knows which field it should be validating. This takes a little bit of work to counteract cross-browser compatibility issues:

function validateField(oEvent) {
    oEvent = oEvent || window.event;
    var txtField = oEvent.target || oEvent.srcElement;
    
    //more code to come
}

The first two lines of code inside this function equalize the differences between event models in IE and DOM-compliant browsers (such as Mozilla Firefox, Opera, and Safari). DOM-compliant browsers pass in an event object to each event handler; the control that caused the event is stored in the event object's target property. In IE, the event object is a property of window; therefore, the first line inside the function assigns the correct value to the oEvent variable. Logical OR (||) returns a non-null value when used with an object and a null object. If you are using IE, oEvent will be null; thus, the value of window.event is assigned to oEvent. If you are using a DOM-compliant browser, oEvent will be reassigned to itself. The second line does the same operation for the control that caused the event, which is stored in the srcElement property in IE. At the end of these two lines, the control that caused the event is stored in the txtField variable. The next step is to create the HTTP request using XMLHttp. This example uses the zXml library (available at www.nczonline.net/downloads/) for cross-browser XMLHttp creation:

function validateField(oEvent) {
    oEvent = oEvent || window.event;
    var txtField = oEvent.target || oEvent.srcElement;
    var oXmlHttp = zXmlHttp.createRequest();
    oXmlHttp.open("get", "ValidateForm.php?" + txtField.name + "=" 
    + encodeURIComponent(txtField.value), true);
    oXmlHttp.onreadystatechange = function () {
        //more code to come
    };
    oXmlHttp.send(null);
}

The XMLHttp object is created and stored in oXmlHttp. Next, the connection is initialized to a GET request using open(). Note that the query string for ValidateForm.php is created by combining the name of the field, an equals sign, and the value of the field (which is URL encoded using encodeURIComponent()). Also note that this is an asynchronous request. This is extremely important for this use case, because you don't want to interfere with the user filling out the rest of the form while you are checking the validity of a single field; remember that synchronous requests made using XMLHttp objects freeze most aspects of the user interface (including typing and clicking) during their execution.. The last part of this function is to handle the response from the server:

function validateField(oEvent) {
    oEvent = oEvent || window.event;
    var txtField = oEvent.target || oEvent.srcElement;
    var oXmlHttp = zXmlHttp.createRequest();
    oXmlHttp.open("get", "ValidateForm.php?" + txtField.name + "=" 
        + encodeURIComponent(txtField.value), true);
    oXmlHttp.onreadystatechange = function () {
        if (oXmlHttp.readyState == 4) {
            if (oXmlHttp.status == 200) {
                var arrInfo = oXmlHttp.responseText.split("||");
                var imgError = document.getElementById("img" 
        + txtField.id.substring(3) + "Error");
                var btnSignUp = document.getElementById("btnSignUp");

                if (!eval(arrInfo[0])) {
                    imgError.title = arrInfo[1];
                    imgError.style.display = "";
                    txtField.valid = false;
                } else {
                    imgError.style.display = "none";
                    txtField.valid = true;
                }
                
                btnSignUp.disabled = !isFormValid();
            } else {
                alert("An error occurred while
                       trying to contact the server.");
            }
        }
    };
    oXmlHttp.send(null);
}

After checking for the correct readyState and status, the responseText is split into an array of strings (arrInfo) using the JavaScript split() method. The value in the first slot of arrInfo will be the value of the PHP variable $valid; the value in the second slot will be the value of the PHP variable $message. Also, a reference to the appropriate error image and the Sign Up button is returned. The error image is gained by dissecting the field name, removing the "txt" from the front (using substring()), prepending "img" and appending "Error" to the end (so for the field "txtBirthday", the error image name is constructed as "imgBirthdayError").

The value in arrInfo[0] must be passed into eval() in order to get a true Boolean value out of it. (Remember, it's a string: either true or false.) If this value is false, the error image's title property is assigned the error message from arrInfo[1], the image is displayed, and the custom valid property of the text box is set to false (this will come in handy later). When a value is invalid, the error image appears, and when the user moves the mouse over it, the error message appears (see Figure 2). If the value is valid, however, the error image is hidden and the custom valid property is set to true.

Figure 2

You'll also notice that the Sign Up button is used in this function. The Sign Up button should be disabled if there is any invalid data in the form. To accomplish this, a function called isFormValid() is called. If this function returns false, the Sign Up button's disabled property is set to true, disabling it. The isFormValid() function simply iterates through the form fields and checks the valid property:

function isFormValid() {
    var frmMain = document.forms[0];
    var blnValid = true;

    for (var i=0; i < frmMain.elements.length; i++) {
        if (typeof frmMain.elements[i].valid == "boolean") {
            blnValid = blnValid && frmMain.elements[i].valid;
        }
    }
    
    return blnValid;
}

For each element in the form, the valid property is first checked to see if it exists. This is done by using the typeof operator, which will return boolean if the property exists and has been given a Boolean value. Because there are fields that aren't being validated (and thus won't have the custom valid property), this check ensures that only validated fields are considered.

The last part of the script is to set up the event handlers for the text boxes. This should be done when the form has finished loading, but only if XMLHttp is supported (because that is how the Ajax validation is being performed here):

//if Ajax is enabled, disable the submit button and assign event handlers
window.onload = function () {
    if (zXmlHttp.isSupported()) {
        var btnSignUp = document.getElementById("btnSignUp");
        var txtUsername = document.getElementById("txtUsername");
        var txtBirthday = document.getElementById("txtBirthday");
        var txtEmail = document.getElementById("txtEmail");

        btnSignUp.disabled = true;
        txtUsername.onchange = validateField;
        txtBirthday.onchange = validateField;
        txtEmail.onchange = validateField;
        txtUsername.valid = false;
        txtBirthday.valid = false;
        txtEmail.valid = false;
        
    }
};

This onload event handler assigns the onchange event handlers for each text box as well as initializes the custom valid property to false. Additionally, the Sign Up button is disabled from the start to prevent invalid data from being submitted. Note, however, that the button will be disabled only if XMLHttp is supported (determined using zXmlHttp.IsSupported()); otherwise, the form will behave as a normal web form and the validation will have to be done when the entire form is submitted.

When you load this example, each of the three validated text fields will make a request to the server for validation whenever their values change and you move on to another field. The user experience is seamless using the Submission Throttling pattern, but the form remains functional even if JavaScript is turned off or XMLHttp is not supported.

Even when using this type of validation, it is essential that all the data be validated again once the entire form is submitted. Remember, if the user turns off JavaScript, you still need to be sure the data is valid before performing operations using it.

This article is adapted from Professional Ajax by Nicholas C. Zakas, Jeremy McPeak, and Joe Fawcett (Wrox, 2006, ISBN: 0-471-77778-1), from chapter 3 "Ajax Patterns."

Copyright 2006 by WROX Publishing, Inc. All rights reserved. Reproduced here by permission of the publisher.



About the Author

Nicholas C. Zakas

Nicholas C. Zakas is the lead author of Professional Ajax by (Wrox, 2006, ISBN: 0-471-77778-1) and the author of Professional JavaScript for Web Developers (Wrox, 2005, ISBN: 0-7645-7908-8). He has worked in web development for more than five years. He has helped develop web solutions in use at some of the largest companies in the world. Nicholas is also an active blogger on JavaScript and Ajax topics at http://www.nczonline.net/.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: November 20, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Are you wanting to target two or more platforms such as iOS, Android, and/or Windows? You are not alone. 90% of enterprises today are targeting two or more platforms. Attend this eSeminar to discover how mobile app developers can rely on one IDE to create applications across platforms and approaches (web, native, and/or hybrid), saving time, money, and effort and introducing apps to market faster. You'll learn the trade-offs for gaining long …

  • IBM Worklight is a mobile application development platform that lets you extend your business to mobile devices. It is designed to provide an open, comprehensive platform to build, run and manage HTML5, hybrid and native mobile apps.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds