Realtime Form Validation Using Ajax

   
By Greg Murray, April 2006  

Articles Index

Editor's Note: This article is taken from the The Java BluePrints Solutions Catalog, a new project from the Java BluePrints program. The Java BluePrints program consists of a set of guidelines and best practices, as well as small applications to illustrate these guidelines on both the J2EE 1.4 and 1.5 platforms.

If you've ever entered form data on the Internet, you've likely encountered cases where server-side processing is used to validate that the data meets a specified set of criteria. JavaScript may be used on the client-side to validate the format of simple data such as email addresses, phone numbers, dates, or credit card numbers. However, client-side processing is limited in that it cannot apply business rules or access server-side data sources to perform that validation.

Consider some of the following use-cases:

  • You need to validate whether a user name has already been registered when creating an online account
  • You need to validate a serial number or coupon code that requires special business logic
  • You need to validate data in conjunction with real-time data that exists on the server or database

Traditional web applications require that the form data be posted to the server. This is followed by some sort of back-end processing of that data, and finally an HTML page refresh that yields the results of the post. However, this strategy can be distracting to the end user, especially in cases where a single form field is invalid and the user has to re-enter a large set of form data. Hence, the problem becomes:

Problem: "How do you validate form data requiring server-side logic without refreshing the HTML page?"

Using Asynchronous JavaScript and XML (Ajax) interactions, data may be validated in near real-time as the user types the data in an HTML form. In this case, form data is validated asynchronously, which allows the HTML page to continue processing events while the form data is being validated by a server-side component in the background. If the form data does not match what is required by the server-side logic, the user knows immediately and can change it without having to refresh the HTML page.

Using the XMLHttpRequest Object and Servlet to Validate Form Data

This solution follows a simple strategy:

  1. First, an Ajax validation on the client is initiated by an HTML JavaScript event, such as a key press in a form field or a form field losing focus. A JavaScript function then creates and configures an XMLHttpRequest object - this object includes the URL to call, the form data to be validated (which may be sent as URL parameters), and a callback function. In this example, the URL the XMLHttpRequest object calls is mapped to a servlet. After sending the XMLHttpRequest, the HTML page continues processing events.
  2. On the server side, the servlet receives the request and extracts the form parameters to be validated using the Java Servlet API. The servlet then applies any necessary business logic to validate the form data, and finally returns an XML document to the XMLHttpRequest object that indicates whether or not the form data is valid.
  3. Once the response is received, the XMLHttpRequest object calls its pre-configured callback function. This function updates the HTML page based on the XML document that was returned from the server. The portions of the page that are updated are re-rendered immediately and the user can see the whether or not the form data is valid

The following images show the direct feedback to the client in the HTML page.

 
Figure 1: A Valid User ID Response

The client will see that the user ID is valid as the ID is being entered.

 
Figure 2: An Invalid User ID Response

The end result: when an invalid user ID is entered, the user will know right away. Notice that the "Create Account" button is also disabled in cases where there are invalid IDs.

A Simple Validation Example

At this point, let's walk through a sample application that uses Ajax to dynamically check form field data before a form is posted. Here is the client and server code needed to perform the interaction.

  • index.jsp - This client-side HTML page contains a simple form that sends Ajax requests to the server in response to key events. These Ajax requests will validate the form contents using a validation servlet.
  • ValidationServlet.java - This is the server side validation code that validates a user ID against a data base (a HashMap for simplicty in this example) using an AJAX requests, and returns the results as an XML document.

The sequence diagram below summarizes the interaction, and can be divided into three parts: client-side requests, server-side processing, and execution of the callback function.

 
Figure 3: Sequence Diagram for Ajax Validation

Client-Side Requests

This HTML input form in the index.jsp page accepts user input which performs real-time validation.

  <input    type="text"
            size="20"  
              id="userid"
            name="id"
    autocomplete="off"
         onkeyup="validateUserId()">
 

Note that we have included the 'autocomplete="off"' attribute. Autocompletion is a browser feature that "remembers" what you entered in previous text form fields with the same ID. Each time you enter text in the form field, the browser attempts to autocomplete your entry by displaying previous entries in a dropdown. This is often a useful feature. However, with real-time form validation, we want to prevent the browser from using autocompletion, so we request that the feature be turned off.

This form calls the validateUserId() JavaScript function every time any key is pressed. (Note that a more comprehensive example may choose to base validation on events other than just "onkeyup" as latency and server load may become an issue. Such an example may also examine which keys are pressed and ignore keys such as the "Control", "Meta", or arrow keys.) The validateUserId() function and the AJAXInteraction object which manages the Ajax interaction appear below.

function AJAXInteraction(url, callback) {

    var req = init();
    req.onreadystatechange = processRequest;
        
    function init() {
      if (window.XMLHttpRequest) {
        return new XMLHttpRequest();
      } else if (window.ActiveXObject) {
        return new ActiveXObject("Microsoft.XMLHTTP");
      }
    }
    
    function processRequest () {
      // readyState of 4 signifies request is complete
      if (req.readyState == 4) {
        // status of 200 signifies sucessful HTTP call
        if (req.status == 200) {
          if (callback) callback(req.responseXML);
        }
      }
    }

    this.doGet = function() {
      // make a HTTP GET request to the URL asynchronously
      req.open("GET", url, true);
      req.send(null);
    }
}

function validateUserId() { 
    var target = document.getElementById("userid"); 
    var url = "validate?id=" + encodeURIComponent(target.value); 
    var ajax = new AJAXInteraction(url, validateCallback); 
    ajax.doGet(); 
}

function validateCallback(responseXML) {
  // see "The Callback Function" below for more details
} 
 

Here, the validateUserId() function creates an AJAXInteraction object by calling new AJAXInteraction(url, validateCallback) with the URL and "validateCallback" callback function. In this example the URL only contains the userid as a URL parameter. You may choose to append more form values as URL parameters to validate on the server. The asynchronous Ajax request is initiated when the ajax.doGet() is called. This example uses the HTTP GET method to send an asynchronous request to the URL "validate". The value of the "userid" text field is sent as an escaped URL parameter with the name "id". On the server, "validate" is a realtive URL mapped to the ValidateServlet.

Server-Side Processing

Once the request reaches the server, the ValidationServlet doGet() method handles the validation requests. First, the id that was submitted is checked against a list of user ids contained in a simple java.util.HashMap accounts, as shown below. A more comprehensive example might check against a database or other naming registry.

    public void init(ServletConfig config) throws ServletException {
        super.init();
        accounts.put("greg","account data");
        accounts.put("duke","account data");
    }
    
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException 
    {
        
        String targetId = request.getParameter("id");
            if ((targetId != null) && !accounts.containsKey(targetId.trim())) {
            response.setContentType("text/xml");
            response.setHeader("Cache-Control", "no-cache");
            response.getWriter().write("true");
        } else {
            response.setContentType("text/xml");
            response.setHeader("Cache-Control", "no-cache");
            response.getWriter().write("false");
        }
    }
 

An invalid user ID on a validation request will result an error message returned to the user. Note that in this example the Content-Type must be set to text/xml as the client XMLHttpRequest object will be expecting XML in this example. In addition, the Cache-Control header should to be set to no-cache to prevent some browser clients from using cached responses (for HTTP 1.0 clients the header name is Pragma).

The Callback Function

After receiving a reply from the server, the client-side XMLHttpRequest object (which is wrapped in the AJAXInteraction object) checks the result of the Ajax call. If the XMLHttpRequest readyState is 4, which signifies a successful Ajax interaction, and the HTTP response code is 200, which indicates a successful HTTP call, then the validateCallback() callback function is invoked.

The XML document received from the server is passed as an argument to the processRequest() function, which is shown below.

function validateCallback(responseXML) {

   var msg = responseXML.
       getElementsByTagName("valid")[0].firstChild.nodeValue;

   if (msg == "false") {

       var mdiv = document.getElementById("userIdMessage");

       // set the style on the div to invalid

       mdiv.className = "bp_invalid";
       mdiv.innerHTML = "Invalid User Id";
       var submitBtn = document.getElementById("submit_btn");
       submitBtn.disabled = true;

    } else {

       var mdiv = document.getElementById("userIdMessage");

       // set the style on the div to valid

       mdiv.className = "bp_valid";
       mdiv.innerHTML = "Valid User Id";
       var submitBtn = document.getElementById("submit_btn");
       submitBtn.disabled = false;
    }  
}
 

The <valid> element, which contains either "true" or "false", is checked and the HTML document and styles are immediately modified according to the results. The CSS styles are externalized from the JavaScript code and defined at the top of the page:

<style type="text/css">
 .bp_invalid {
    color:white;
    background:red;
 }
 .bp_valid {
    color:green;
 }
</style>
 

If the client receives a <valid>false</valid> message, it updates the contents of the <div id="userIdMessage"> element with an error message and the contents and style of the element are set using the following statements:

mdiv.className = "bp_invalid"
mdiv.innerHTML = "Invalid User Id" 
 

If the call is successful, the userIdMessage is retrieved using this statement:

document.getElementById("userIdMessage")
 

And the contents and style of the element are set as follows:

mdiv.className = "bp_valid"
mdiv.innerHTML = "Valid User Id" 
 

The HTML document will then be re-rendered automatically, displaying the appropriate message.

    <tr>
     <td><b>User Id:</b></td>
     <td>
      <input type="text"
                size="20"  
                  id="userid"
                name="id"
         autocomplete="off"
             onkeyup="validateUserId()">
     </td>
     <td>
      <div id="userIdMessage"></div>
     </td>
    </tr>
 

The HTML snippet above shows the <div id="userIdMessage"> element appearing near the form field to show real-time validation results.

Submitting the Validated User ID

Finally, when the user clicks the "Create Account" button, the form is sent to the URL "validate" using the HTTP POST method. The ValidateServlet doPost() method on the server then processes the request.

    public void doPost(HttpServletRequest request,
                       HttpServletResponse  response)
        throws IOException, ServletException
     {

        String targetId = request.getParameter("id");

        if ((targetId != null) &&
            !accounts.containsKey(targetId.trim()))
        {

            accounts.put(targetId.trim(), "account data");
            request.setAttribute("targetId", targetId);
            context.getRequestDispatcher("/success.jsp").
                forward(request, response);

        } else {

            context.getRequestDispatcher("/error.jsp").
                forward(request, response);

        }
    }
 

Here, the doPost() method extracts the user ID parameter from the request and adds it to the list of registered user IDs. Consequently, an attempt to enter that user ID will result in an error on subsequent requests. The client will then be forwarded to a simple success or error page depending on whether the id could be added or not.

Note that even though the Ajax functionality has reported that a user ID is valid, the code still checks that the user id has not been added to the list. This is a safety precaution, just in case a third-party registered that ID between the client's validation request and the form submission. Form data should always be revalidated prior to further on the server-side processing. Failure to revalidate form data may result in security holes or inconsistent state on the server.

For More Information
About the Author

Greg Murray is an engineer with Sun Microsystems where he is a member of the Java BluePrints program. He will be the specification lead for Servlet 2.5. He is an author of the Addison-Wesley Java-series books, Designing Enterprise Applications with the Java 2 Platform, Enterprise Edition and Designing Web Services with the J2EE 1.4 Platform

Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
Your email address (no reply is possible without an address):
Sun Privacy Policy

Note: We are not able to respond to all submitted comments.