with Ges Seger

STRAAANGE COOODE

Today's Episode:

Check It Out!

The more I work at my day job, the more I'm forced to work with my coworker Todd's greatest creation -- a Javascript library that was originally built to handle the task of enabling or disabling forms based on the application's current mode of operation. Since my previous Strange Code adventure into this library, I have had to discover and use some of its more esoteric functions. Specifically, I now needed to use the routines which Todd had programmed to validate user data.

Now, there are several different solutions to the problem of form validation in Javascript. The one usually seen in books and other websites involves defining an object for each form element on page load, then writing some sort of driver script to loop over all these objects when the form is submitted. This was the approach that Todd took to a Lovecraftian extreme in his library. There's nothing wrong with this technique -- except for two things:

  1. it requires some foreknowledge of the form that is being validated
  2. it requires non-trivial initialization when the form is loaded.

THE SOLUTION

Inspiration actually hit me while I was at work this time. While staring at some HTML markup elsewhere in the project, it suddenly occurred to me that I could define a CSS class corresponding to the appropriate data type I was attempting to validate. It didn't matter what style rules this class had; what I was interested in was that it was associated with certain form elements. I could then access the classType property of those form element in a Javascript driver to determine how to validate the element's data -- or report why it failed validation. No prior knowledge of this form is required, no initialization is neccessary at page load, and it's easily extensible to cover whatever data types your heart desires.

WALKTHROUGH

Let's go to the code.

 0 function validate(n) {
 1   var f = document.forms[n];
 2   var l = f.elements.length;
 3
    

Here are the usual tricks I use in these code snippets. Line 1 is what I usually use to get a reference to the target form. Line 2 caches the number of elements in the form so we don't have to keep reaching into the form object on each pass through our upcoming validation loop.

 4   for(i = 0; i < l; i++) {
 5
 6     with (f.elements[i]) {
 7
 8       // -- we're only interested in text fields
 9       if (! type.match(/text/i)) continue;
10
    

Line 4 starts our loop over the form elements. Line 6 is our usual salue to laziness and prevention of repetitive-stress injury. Line 9 is our first check on the element. The form element type with the most danger of receiving bogus input from the luser are text fields and text areas. So, we test the element type property by attempting to match the regular expression /text/i against it. If the type is not either text or textarea, we abort further processing for this iteration of the loop and go to the next element.

11       // -- check if field was required and empty
12       if (className.match(/required/i) && value == '') {
13         alert('Required field ' + name + ' was empty');
14         return 0;
15       }
16
    

Now come our next checks. Line 12 tests for two things:

  1. Whether the class required was used in the field definition
  2. Whether the field value is empty

If both checks return true, the user left a required input field empty. We report the error in line 13, and return 0 to the calling handle in line 14 to abort further processing of the form.

17       // -- bump field data against appropriate validating regexp
18       var fmt = className.match(/float|integer|alpha|char/);
19       if (fmt == 'float'   && value.match(/^\d+\.\d+$/))  continue;
20       if (fmt == 'integer' && value.match(/^\d+$/))       continue;
21       if (fmt == 'alpha'   && ! value.match(/\W/g))       continue;
22       if (fmt == 'char'    && value.match(/^[a-zA-Z]+$/)) continue;
23
    

The money code is contained in this block. Line 18 tests the field's className property for the types of data we want to process, and captures any match to the variable fmt. Lines 19-22 successively test fmt against each data type we're validating against, and selects a validating regexp to test against the field value accordingly. The logical tests are structured so that a successful match against a validating regexp aborts further processing in this loop iteration.

The code is somewhat colvoluted at this point, but much less convoluted than it would have been had I not put line 18 in and instead attempted a match against the className in lines 19-22. First versions of this code did just that, and I quickly discovered that I lacked the precision and granularity neccessary to report any errors back to the luser.

This is also the block you need to modify if there are data types in your form beyond these four. Remember to write your test in such a manner that a successful validation will jump to the next loop iteration.

24       // -- didn't match any of the regexp's.  Bail out!
25       alert('Field ' + name + ' not in ' + fmt + ' format');
26       return 0;
27
    

If none of the validating regexps match, we've got trouble. Line 25 pops up an alert box telling the luser which field failed which validation. Remember my comment in the previous block about the code originally lacking precision and granularity? By capturing the field's data type in line 18, we are able to tell the luser in line 25 exactly which data type they were supposed to use in the field that failed validation. Line 26 returns 0 to the calling handler so the form data won't be submitted to the server.

28     }
29
30   }
31
32   // -- data OK.  Tell browser to submit form data
33   return 1;
34 }
    

If we hit this section of the code, all the form data has passed validation. Line 33 returns 1 to the calling handler to allow the form to submit the data to the server.

USAGE

Install the following code in an onclick handler of a form button -- preferably the one that submits the data to the server:

      onclick="return validate(0)"
    

It is neccessary for this handler to return TRUE in order for form submission to continue. A return of FALSE will abort form submission and leave you on that page to correct the error of your ways.

BUGS FEATURES

As with previous Strange Code practice, I pass the number of the form into validate() and let that function access the document.forms[] collection to determine which form it should be validating. There are probably more robust ways of doing this, but I haven't experimented with them.

The code bails out on the first error it finds. This may be a problem if you have invalid data in several fields at the same time.

Anyone coming from a Perl 5 background should be aware there are some subtle differences in Javascript's regular expression package that will trip up the unwary. The code in line 18, for instance, would require a set of capturing parenthesis in Perl 5 to work the way it's been coded in Javascript.

EXAMPLE

Class-Based Data Validation
Field Input Description
numeric1 Floating-point number input:
numeric2 Integer number input
string1 Alphanumeric string input
string2 Non-numeric string input