Team LiB
Previous Section Next Section

Form Validation

One of the most useful things you can do in JavaScript is check to make sure that a form is filled in properly. Checking form contents before submission saves server processor cycles as well as the user’s time waiting for the network round trip to see if the proper data has been entered into the form. This section provides an overview of some common techniques for form validation.

The first issue to consider with form validation is when to catch form fill-in errors. There are three possible choices:

  1. Before they happen (prevent them from happening)

  2. As they happen

  3. After they happen

Generally, forms tend to be validated after input has occurred, just before submission. Typically, a set of validation functions in the form’s onsubmit event handler is responsible for the validation. If a field contains invalid data, a message is displayed and submission is canceled by returning false from the handler. If the fields are valid, the handler returns true and submission continues normally.

Consider the brief example here that performs a simple check to make sure that a field is not empty:

<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">>
<<html>>
<<head>>
<<title>>Overly Simplistic Form Validation<</title>>
<<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />>
<<script type="text/javascript">>
<<!--
function validate()
{
  if (document.myform.username.value == "")
    {
        alert("Username is required");
        return false;
    }
  return true;
}
//-->>
<</script>>
<</head>>
<<body>>
<<form name="myform" id="myform" method="get"
      action="http://www.javascriptref.com/"
      onsubmit="return validate();">>
Username: 
<<input type="text" name="username"  id="username" size="30" />>
<<input type="submit" value="submit" />>
<</form>>
<</body>>
<</html>>

The previous example suffers from numerous deficiencies. First off, it really doesn’t check the field well. A single space is acceptable using this validation. Second, it is not terribly abstract in that the validation function works with only the username field in that document; it can’t be applied to a generic field. Last, the validation doesn’t bring the field that is in error into focus. A better example correcting all these deficiencies is presented here:

<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">>
<<html>>
<<head>>
<<title>>Better Form Validation<</title>>
<<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />>
<<script type="text/javascript">>
<<!--
// Define whitespace characters
var whitespace = " \t\n\r";
function isEmpty(s)
{
   var i;
   if((s == null) || (s.length == 0))
      return true;
   // Search string looking for characters that are not whitespace
   for (i = 0; i << s.length; i++)    
    {   
      var c = s.charAt(i);
      if (whitespace.indexOf(c) == -1) 
        return false;
    }
    // At this point all characters are whitespace.
    return true;
}

function validate()
{
  if (isEmpty(document.myform.username.value))
    {
        alert("Error: Username is required.");
        document.myform.username.focus();
        return false;
    }
  if (isEmpty(document.myform.userpass.value))
    {
        alert("Error: Non-empty password required.");
        document.myform.userpass.focus();
        return false;
    }
  return true;
}
//-->>
<</script>>
<</head>>
<<body>>
<<form name="myform" id="myform" method="get"
      action="http://www.javascriptref.com"
      onsubmit="return validate();">>
Username: 
<<input type="text" name="username" id="username" 
       size="30" maxlength="60" />>
<<br />>
Password: 
<<input type="password" name="userpass" id="userpass" 
       size="8" maxlength="8" />>
<<br />>
<<input type="submit" value="Submit" />>
<</form>>
<</body>>
<</html>>

Abstracting Form Validation

The previous example illustrated how writing generic input validation routines can be useful. Instead of having to recode the same or similar field checking functions for each form on your site, you can write a library of validation functions that can be easily inserted into your pages. In order to be reusable, such functions should not be hardcoded with form and field names. The validation functions should not pull the data to be validated out of the form by name; rather, the data should be passed into the function for checking. This allows you to drop your functions into any page and apply them to a form using only a bit of event handler “glue” that passes them the appropriate fields.

Form checking functions should go beyond checking that fields are non-empty. Common checks include making sure a field is a number, is a number in some range, is a number of some form (such as a U.S. ZIP code or Social Security number), is only a range of certain characters like just alpha characters, and whether input is something that at least looks like an e-mail address or a credit card number. Many of the checks, particularly the e-mail address and credit card number checks, are not really robust. Just because an e-mail address looks valid doesn’t mean it is. We’ll present e-mail and numeric checks here as a demonstration of common validation routines in action.

Note 

Regular expressions are an invaluable tool for form validation because they let you check input strings against a pattern using very little code. Without them, you’d be stuck writing complex string parsing functions manually. We’ll use a combination of manual techniques and regular expressions. Observe how much easier it is to use regexps.

Many forms are used to collect e-mail addresses, and it is nice to ferret out any problems with addresses before submission. Unfortunately, it is difficult to guarantee that addresses are even in a valid form. In general, about the best you can say quickly about an e-mail address is that it is of the form userid@domain, where userid is a string and domain is a string containing a dot. The “real” rules for what constitutes a valid e-mail address are actually quite complicated, and take into consideration outdated mail addressing formats, IP addresses, and other corner cases. Because of the wide variation in e-mail address formats, many validation routines generally look simply for something of the form string@string. If you want to be extremely precise, it is even possible not to have a dot (.) on the right side of an e-mail! The function here checks the field passed in to see if it looks like a valid e-mail address.

function isEmail(field)
{ 
  var positionOfAt;
  var s = field.value;
  if (isEmpty(s))
    {
       alert("Email may not be empty");
       field.focus();
       return false;
    }
  
  positionOfAt = s.indexOf('@',1);
  if ( (positionOfAt == -1) || (positionOfAt == (s.length-1)) )
    {
       alert("E-mail not in valid form!");
       field.focus();
       return false;
    }
  return true;
}

We can write this more elegantly using a regular expression:

function isEmail(field) 
{
  var s = field.value;
  if (isEmpty(s))
    {     
       alert("Email may not be empty");
       field.focus();
       return false;
    }
  if (/[^@]+@[^@]+/.test(s))
       return true;
   alert("E-mail not in valid form!");
   field.focus();
   return false;
}

The regular expression above should be read as “one or more non-@ characters followed by an @ followed by one or more non-@ characters.” Clearly, we can be more restrictive than this in our check if we like. For example, using /[^@]+@(\w+\.)+\w+/ does a better job. It matches strings with characters (e.g., “john”) followed by an @, followed by one or more sequences of word characters followed by dots (e.g., “mail.yahoo.”) followed by word characters (e.g., “com”).

Checking numbers isn’t terribly difficult either. You can look for digits and you can even detect if a passed number is within some allowed range. The routines here show a way of doing just that:

function isDigit(c)
{   
 return ((c >>= "0") && (c << "9"))
 // Regular expression version:
 // return /^\d$/.test(c);
}

Since the isDigit() routine is so simple, the regular expression version isn’t much better. But consider this more complicated example:

function isInteger(s)
{ 
  var i=0, c;
  if (isEmpty(s)) 
    return false;

  if (s.charAt(i) == "-")
     i++;
  for (i = 0; i << s.length; i++)   
   { 
    // Check if all characters are numbers
    c = s.charAt(i);
    if (!isDigit(c)) 
      return false;
   }
  return true;
}

The regular expression version is far more elegant:

function isInteger(s) 
{
  return /^-?\d+$/.test(s);
}

The regexp used should be read, “at the very beginning of the string is an optional negative sign followed by one or more digits up to the end of the string.”

Note 

You could also write a similarly elegant isInteger() function by passing the string data to parseInt() and checking whether NaN is returned.

Since regular expressions are only useful for pattern matching, they are of limited value in some situations:

function isIntegerInRange (s,min,max)
{ 
  if (isEmpty(s))
    return false;

  if (!isInteger(s)) 
    return false;
  var num = parseInt (s);
  return ((num >>= min) && (num << max));
}

Drop-in Form Validation

The last question is how these routines can be easily added in to work with any form. There are many ways to do this. In the next example we use an array holding the names of the fields and the type of validation required.You would then loop through the array and apply the appropriate validation routine, as shown here:

<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">>
<<html>>
<<head>>
<<title>>Generic Form Check Demo<</title>>
<<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />>
<<script type="text/javascript">>
<<!--
var validations = new Array();
// Define which validations to perform. Each array item
// holds the form field to validate, and the validation
// to be applied. This is the only part you need to
// customize in order to use the script in a new page!

validations[0]=["document.myform.username", "notblank"];
validations[1]=["document.myform.useremail", "validemail"];
validations[2]=["document.myform.favoritenumber", "isnumber"];

// Customize above array when used with a new page.
function isEmpty(s) 
{
  if (s == null || s.length == 0)
    return true;

  // The test returns true if there is at least one non-
  // whitespace, meaning the string is not empty. If the
  // test returns true, the string is empty.
  return !/\S/.test(s);
}

function looksLikeEmail(field)
{
  var s = field.value;

  if (isEmpty(s))
   {
     alert("Email may not be empty");
     field.focus();
     return false;
   }

  if (/[^@]+@\w+/.test(s))
       return true;
  alert("E-mail not in valid form.");
  field.focus();
  return false;
}

function isInteger(field)
{
  var s = field.value;
  if (isEmpty(s))
   {
     alert("Field cannot be empty");
     field.focus();
     return false;
   }

  if (!(/^-?\d+$/.test(s)))
   {
     alert("Field must contain only digits");
     field.focus();
     return false;
   }
 return true;
}

function validate()
{
  var i;
  var checkToMake;
  var field;

  for (i = 0; i << validations.length; i++)
   {
     field = eval(validations[i][0]);
     checkToMake = validations[i][1];
     switch (checkToMake)     
      {
       case 'notblank': if (isEmpty(field.value))
                          {
                           alert("Field may not be empty");
                           field.focus();
                           return false;
                          }
                        break;
       case 'validemail':  if (!looksLikeEmail(field))
                               return false;
                           break;
       case 'isnumber':  if (!isInteger(field))
                            return false;
      }
   }
  return true;
}
//-->>
<</script>>
<</head>>
<<body>>
<<form name="myform" id="myform" method="get"
 action="http://www.javascriptref.com"
 onsubmit="return validate();">>

Username: 
<<input type="text" name="username" id="username"
           size="30" maxlength="60" />>
<<br />>
Email: 
<<input type="text" name="useremail" id="useremail"
        size="30" maxlength="90" />>
<<br />>
Favorite number: 
<<input type="text" name="favoritenumber"
       id="favoritenumber" size="10" maxlength="10" />>
<<br />>
<<input type="submit" value="submit" />>
<</form>>
<</body>>
<</html>>

The nice thing about this approach is that it’s easy to add these validation routines to just about any page. Just place the script in the page, customize the validations[] array to hold the form fields you wish to validate and the string to indicate the validation to perform, and finally add the call to validate() as the onsubmit handler for your form. Separating the mechanism of validation (the checking functions) from the policy (which fields to check for what) leads to reusability and decreased maintenance costs in the long run.

Form Validation via Hidden Fields

An even more elegant possibility is to use hidden form fields and (believe it or not) routines that are even more generic than those we just saw. For example, you might define pairs of fields like this:

<<input type="hidden" name="fieldname_check"
 value="validationroutine">>
<<input type="hidden" name="fieldname_errormsg"
 value="msg to the user if validation fails">>

You would define hidden form fields for each entry to validate, so to check that a field called username is not blank, you might use

<<input type="hidden" name="username_check" value="notblank">>

<<input type="hidden" name="username_errormsg"
 value="A username must be provided">>

To check for an e-mail address, you might use

<<input type="hidden" name="email_check" value="validEmail">>
<<input type="hidden" name="email_errormsg"
 value="A valid email address must be provided">>

You would then write a loop to look through forms being submitted for hidden fields and to look for ones in the form of fieldname_check. When you find one, you could use string routines to parse out the field name and the check to run on it. If the check fails, you can easily find the associated error message to show by accessing the field fieldname_errormsg.

Note 

One of the main reasons the hidden field approach is more elegant is that we can easily have the server-side of the Web equation look at the hidden values passed and run similar validation checks. This double-checking may seem a waste of time, but it actually improves security as it is not possible to truly know if client-side validation in JavaScript was run.

Regardless of the method you choose, it should be clear that the approach is useful as it allows you to separate out reused JavaScript validation functions into .js files and reference from just about any form pages. However, before setting out on the task to roll your own validation routines, consider the number of people who already have needed to do the same thing. Code is out on the Web already, so it makes sense to start with a library when making your validation code. For example, take a look at http://developer.netscape.com/docs/examples/javascript.html for some sample scripts. Netscape has provided a form validation collection of code ever since JavaScript 1.0 and also provides regular expression-oriented checks as well.

onchange Handlers

There is no reason you need to wait for the form to be submitted in order to validate its fields. You can validate a field immediately after the user has modified it by using an onchange event handler. For example:

<<script type="text/javascript">>
<<!--
function validateZip(zip) 
{
   if (/\d{5}(-\d{4})?/.test(zip)) 
     return true;
   alert("Zip code must be of form NNNNN or NNNNN-NNNN");
   return false;
}
// -->>
<</script>>
  ...
<<form action="#" method="get">>
<<input type="text" name="zipcode" id="zipcode"
 onchange="return validateZip(this.value);" />>

...other fields...
<</form>>

The validateZip() function is invoked when the ZIP code field loses focus after the user changed it. If the ZIP code isn’t valid, the handler returns false, causing the default action (blurring of the field) to be canceled. The user must enter a valid ZIP code before they will be able to give focus to another field on the page.

Preventing the user from giving focus to another field until the most recently modified field is correct is questionable from a usability standpoint. Often, users might want to enter partial information and then come back to complete the field later. Or they might begin entering data into a field by mistake, and then realize they don’t want any data to go in that field after all. Having the focus of input “trapped” in one form field can be frustrating for the user. For this reason, it is best avoided. Instead, alert the user to the error, but return true from the onchange handler anyway allowing them to move along in the form.

Keyboard Masking

We’ve seen how to catch errors at submission time and right after they occur, but what about preventing them in the first place? JavaScript makes it possible to limit the type of data that is entered into a field as it is typed. This technique catches and prevents errors as they happen. The following script could be used in browsers that support a modern event model (as discussed in Chapter 11). It forces the field to accept only numeric characters by checking each character as it is entered in an onkeypress handler:

<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">>
<<html>>
<<head>>
<<title>>Numbers-Only Field Mask Demo<</title>>
<<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />>
<<script type="text/JavaScript">>
<<!--
function isNumberInput(field, event) 
{
  var key, keyChar;

  if (window.event)
    key = window.event.keyCode;
  else if (event)
    key = event.which;
  else
    return true;
  // Check for special characters like backspace
  if (key == null || key == 0 || key == 8 || key == 13 || key == 27)
    return true;
  // Check to see if it's a number
  keyChar =  String.fromCharCode(key);
  if (/\d/.test(keyChar)) 
    {
     window.status = "";
     return true;
    } 
  else 
   {
    window.status = "Field accepts numbers only.";
    return false;
   }
}
//-->>
<</script>>
<</head>>
<<body>>
<<form name="testform" id="testform" action="#" method="get">>
Robot Serial Number:
<<input type="text" name="serialnumber" id="serialnumber"
 size="10" maxlength="10"
 onkeypress="return isNumberInput(this, event);" title="Serial number contains only digits" />>
<</form>>
<</body>>
<</html>>

In this script, we detect the key as it is pressed and look to see if we will allow it or not. We could easily vary this script to accept only letters or even convert letters from lower- to uppercase as they are typed.

The benefit of masking a field is obviously that it avoids having to do heavy validation later on by trying to stop errors before they happen. Of course, you need to let users know that this is happening, by both clearly labeling fields and using advisory text (and even giving an error message, as we did by setting the window status message). You might consider using an alert dialog or putting an error message into the form, but that might be too obtrusive.

Validation Best Practices

Form validation is really a great use of JavaScript, but sometimes it is misused or poorly applied. This section outlines some general principles you can apply to your validation strategy.

  • Be helpful. Client-side validation should be used to assist the user in entering data correctly. As such, it should interact with the user in ways that are helpful. For example, if the user enters invalid data, include the format data was expected to be in your error message. Similarly, use script to correct common mistakes when you can. For example, it’s simple to use JavaScript to automatically reformat phone numbers of the form NNN-NNN-NNNN to (NNN) NNN-NNNN.

  • Don’t be annoying. We’ve used alert()s to inform users of invalid inputs for the sake of illustration. However, alert()s have to be dismissed before the user can correct their data, and users might forget which fields were in error. Instead, consider showing the error message somewhere in the page itself.

  • Use HTML features instead of JavaScript whenever possible. Rather than using JavaScript to validate the length of a field, use maxlength. Instead of checking a date, provide a pull-down of the possible dates so as to avoid bad entries. The same could be done for typing in state codes or other established items.

  • Show all the errors at once. Many people prefer to see all the errors at once, so you could collect each individual error string into an error message and display them all together.

  • Catch errors early. Waiting until submission is not the best time to catch errors. Some developers will opt instead to catch errors when fields are left using the onblur or onchange handler. Unfortunately, onblur doesn’t always work as planned because you may get into an endless event loop. If you do use blur and focus triggers, make sure to manage events, including killing their bubble (as discussed in Chapter 11).

  • If in doubt, be more permissive rather than more restrictive. There’s nothing more frustrating than trying to enter information you know is valid only to have it rejected because the page’s developer isn’t aware of all the possible inputs. Remember: JavaScript form validation is to be used to help the user find mistakes, not to enforce policy.

A final observation that escapes many developers is that you always need to validate form fields at the server. Client-side validation is not a substitute for server-side validation; it’s a performance and usability improvement because it reduces the number of times the server must reject input. Always remember that users can always turn off JavaScript in their browser or save a page to disk and edit it manually before submission. This is a serious security concern and JavaScript developers would be mistaken to think their validation routines will keep the determined from injecting bad data into their Web application.


Team LiB
Previous Section Next Section