Better Form Validation Through Clever Javascript

Client-side form validation is a pretty regular thing nowadays.  Every site you visit most likely does things slightly differently from adding a red star to providing a cryptic summary box at the top of the screen to simply sending the customer to an error screen no developer really thought would make it to production.

Today I’ll outline a nice method for using YUI.

I’m a firm believer in separation of concerns, so our solution needs to be able to be separated from our markup or dropped into an existing application without too much trouble.

Step 1:

Let’s decorate our HTML markup with a “magic” HTML class attribute, say “x-required”.  Here’s an example:

<input type="text" class="x-required">

Step 2

Now, we’ll use Javascript to dynamically get all of our required elements upon page load:

YAHOO.util.Event.onDOMReady( function{
  var controls = YAHOO.util.Dom.getElementsByClassName( 'x-required', 'input' );
} );

Now that we’ve selected the elements we want to apply a requirement check, let’s decide what behavior to apply. In the case of a required input field, marking the element with a class of “x-error” (and any associated label elements is a good start. More advanced methods may dynamically create a summary box at the top of the screen or apply per-element markup to indicate the requirement. For today, let’s just add a new class and let CSS mark it red.

.x-error { background-color: #900; color: #fff; }

Above is our CSS rule for an error. Let’s apply this class to any input elements that are required whenever they are blurred (and of course, blank).

YAHOO.util.Event.onDOMReady( function{
  var controls = YAHOO.util.Dom.getElementsByClassName( 'x-required', 'input' );
  for ( var i = 0; i < controls.length; i++ ) {
    YAHOO.util.Event.addListener(
      controls[i],
      'blur',
      // here's the function that we'll call on blur
      function( t, o ) {
        // trim the white space off the beginning
        // and end of the value of our input field
        if ( '' == o.value.replace( /^\s\+|\s+$/g, '' ) ) {
          // if our post-trimmed value is an empty string
          // mark it as an error
          YAHOO.util.Dom.addClass( o, 'x-error' );
        } else {
          // just to be safe, remove the 'x-error' class
          YAHOO.util.Dom.removeClass( o, 'x-error' );
        }
      },
      controls[i]
    );
  }
} );

Okay, that looks like a nice chunk of code.  here’s what it does:

  1. finds all elements in our DOM tree with the class of x-required
  2. Using the YAHOO.util.Event object, we’ll attach a listener to each input we found in step 1 so that something will happen whenever you blur that element
  3. our blur function uses a regular expression to trim white-space from the input field
  4. if the trimmed value is an empty string, apply the x-error class
  5. otherwise, remove the x-error class

Step 3

But what about when the form gets submitted?  Our blur function won’t fire.  Let’s use a similar technique to attach a listener to the parent form’s onSubmit handler

YAHOO.util.Event.onDOMReady( function{
  var controls = YAHOO.util.Dom.getElementsByClassName( 'x-required', 'input' );
  for ( var i = 0; i < controls.length; i++ ) {
    // gets the parent form element
    var parentForm = YAHOO.util.Dom.getAncestorByTagName( controls[i], 'form' );
    YAHOO.util.Event.addListener(
      parentForm,
      'submit',
      function( t, o ) {
        if ( '' == o.value.replace( /^\s\+|\s+$/g, '' ) ) {
          YAHOO.util.Event.preventDefault( t );
          YAHOO.util.Dom.addClass( o, 'x-error' );
        }
      },
      controls[i]
    };
  }
} );

Here, we’re now using the YUI Dom object to determine at runtime the parent form of one of our magic input elements.

Similar to the blur handler, we’ll attach a listener to the submit event of the parent form.

In this case, we’ll prevent the default behavior of the event (the YAHOO.util.Event.preventDefault call) if the validation fails. We’ll also apply the error class to the offending element as well.

Last touches

You may notice that there’s a bit of redundancy taking place here: the addClass call and the regex appears in both event handlers. We’ll refactor now and put the creation of the regex before the for loop for a slight performance boost, as well as put the addClass and removeClass calls into a wrapper function (which will be nice for when we decide adding a class isn’t what we want to do).

Putting it all together

Here’s the finished product:

YAHOO.util.Event.onDOMReady( function{
  var trimRegex = /^\s\+|\s+$/g;
  var markError = function( obj ) {
    // add the class 'x-error' to the obj
    YAHOO.util.Dom.addClass( obj, 'x-error' );
  };
  var clearError = function( obj ) {
    // remove the class 'x-error' to the obj
    YAHOO.util.Dom.removeClass( obj, 'x-error' );
  };
  var controls = YAHOO.util.Dom.getElementsByClassName( 'x-required', 'input' );
  for ( var i = 0; i < controls.length; i++ ) {
    // gets the parent form element
    var parentForm = YAHOO.util.Dom.getAncestorByTagName( controls[i], 'form' );
    YAHOO.util.Event.addListener(
      parentForm,
      'submit',
      function( t, o ) {
        if ( '' == o.value.replace( trimRegex, '' ) ) {
          YAHOO.util.Event.preventDefault( t );
          markError( o );
        }
      },
      controls[i]
    };
    YAHOO.util.Event.addListener(
      controls[i],
      'blur',
      // here's the function that we'll call on blur
      function( t, o ) {
        if ( '' == o.value.replace( trimRegex, '' ) ) {
          markError( o );
        } else {
          clearError( o );
        }
      },
      controls[i]
    );
  }
} );

Future considerations

You can see how this technique can be applied to a large number of elements that have the right magic class name set. An idea to improve this technique is to make the markError and clearError functions a bit more descriptive on your pages.  I suggest also finding their associated label (you are using labels, aren’t you?) and making its class change, and/or grabbing the text out of it for use in an error and/or appending some text to the label telling the user the control is required.

I’ve also written a similar bit ‘o’ code that defines required groups of controls, so you can have a group of three input elements that are defined such that if any one of them have a value they all need a value.

This entry was posted in javascript, web. Bookmark the permalink.

3 Responses to Better Form Validation Through Clever Javascript

  1. Hi Josh

    Thanks for the script! There appears to be some issues with your in firefox at least, perhaps formatting issues. There are a bunch of utf-8 quotes that I ended up changing – not sure if that was necessary.

    I had to add “()” after the function on the first line as I was getting the following error “missing ( before formal parmeters”

    The lines with the trimRegex aren’t real good. Probably easier for me to list my working script (hopefully it will format correctly in the comment) :

    YAHOO.util.Event.onDOMReady( function(){
    var trimRegex = /^\s\+|\s+$/g;
    var markError = function( obj ) {
    // add the class ‘x-error’ to the obj
    YAHOO.util.Dom.addClass( obj, ‘x-error’ );
    };

    var clearError = function( obj ) {
    // remove the class ‘x-error’ to the obj
    YAHOO.util.Dom.removeClass( obj, ‘x-error’ );
    };

    var controls = YAHOO.util.Dom.getElementsByClassName( ‘x-required’, ‘input’ );
    for ( var i = 0; i < controls.length; i++ ) {
    // gets the parent form element
    var parentForm = YAHOO.util.Dom.getAncestorByTagName( controls[i], ‘form’ );
    YAHOO.util.Event.addListener(
    parentForm,
    ‘submit’,
    function( t, o ) {
    if ( ! o.value.replace( trimRegex ) ) {
    YAHOO.util.Event.preventDefault( t );
    markError( o );
    }
    },
    controls[i]
    );

    YAHOO.util.Event.addListener(
    controls[i],
    ‘blur’,
    // here’s the function that we’ll call on blur
    function( t, o ) {
    if ( ! o.value.replace( trimRegex ) ) {
    markError( o );
    } else {
    clearError( o );
    }
    },
    controls[i]
    );
    }
    });

  2. Josh Peters says:

    Wordpress always messes up my inline code :(

    Thanks for your comment, I’ll take some time tomorrow and dig in.

  3. Josh Peters says:

    Hey Bruce, just looked at things again. The quotes were mucked up by WordPress. You’re correct about the missing “) {“; no idea why that was cut off.

    I like what you did with the replace() call, but I’m always a bit worried that I’ll forget how javascript works with function calls so I try to keep them with the same style ( i.e. foo( null ) versus foo() ).

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Spam Protection by WP-SpamFree