Wrox Home  
Search
Professional Ajax
by Nicholas C. Zakas, Jeremy McPeak, Joe Fawcett
March 2007, Paperback


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
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." Nicholas is also the author of Professional JavaScript for Web Developers and 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. His previous articles on wrox.com include Ajax and the Yahoo! Connection Manager and XMLHttp Requests for Ajax which is an excerpt from chapter 2 "Ajax Basics" of Professional Ajax.