Invalid content

Improved JavaScript Validation (repost)

Posted by Matt on Sun, Jan 31 2010

This article was written by me and originally published on February 12, 2008 on PHP Architect's C7Y site.  Since that site is no longer around (meaning this article is currently homeless) and their one year exclusive rights expired long ago, I figured why not re-post it here.  The concepts and code in this article eventually evolved into my CakePHP JS Validation Plugin.

 

Your social video ad widget mashup is ready to be unleashed to the public.  Conscious of all the latest SQL injection and cross scripting attacks, you've locked down every form field.  Before the big launch, you enlist your buddy/co-worker/significant to do some user testing. 

After being impressed with your innovated use of a gradient within a gradient, they manage to find the user registration form.  It's just two fields, username and password.  What could go wrong here?

Your tester is feeling spunky and enters the username "D'shiz" and clicks the Join button.  The submission is whisked off across the internet to your co-located server where your stringent validation catches the disallowed apostrophe in the username.

You give a small fist pump when you see the form returned.  Across the top is a concise, highlighted error message detailing the problem.  To make sure there is absolutely no confusion you also mark the username label red.  Your tester has no trouble figuring out the issue and changes their username to "Dshiz".  Again the form is sent and again it is returned.  This time with the error "Your username must be at least 6 characters long".  Your tester changes their identity to "Dshiz49" and re-re-submits.  Ooops.  They forget to re-enter their password.  Your app had been clearing it out every time as part of your uber-security approach.

Finally the fourth submit gets it right, but in the process you wasted three page loads and the precious time of your test user.  So how can we improve this scenario?  Ajax validation is one option, but that can be overkill for many tests.  None of the rules used in this example need the server or database to validate.  If you were checking the availability of a username, that would be another story.

 

JavaScript validation has been around for years, but it's often poorly done to the point where it detracts, rather than add, value.

Let's lay out some requirements for good JavaScript validation.

Seamless To The Developer

First, keep the programmers happy.  It doesn't make sense to maintain two sets of validation rules.  Instead pull the rules from the same source that is used for server side validation and convert them to JavaScript for the client side.  Obviously not all rules can be applied with JavaScript, so you'll need to filter them.   If your rules are a mix of functions and regular expressions, you can use PHP's function_exists function to get just the regular expressions.

Seamless To The User

JavaScript alerts are not seamless.  They are annoying.  To be seamless, the feedback to the user must look the same as if the user submitted the form and the server responded.  In this scenario, we'll assume you're using an unordered list with the id "errors" to display the messages.The first step to building re-useable client side validation is organizing your validation rules.  Regular expressions are great, since they can handle many different validations and work in both PHP and JavaScript.  Here a couple that will help us in the example above.

define('RULE_NOT_EMPTY', '.{1,}');
  define('RULE_ALPHANUMERIC', '^[\w\d]{0,}$');

Hopefully you have a model class with all your rules defined or some other method of keeping them organized.   Unfortunately, everyone's system will be different, so for this article we'll want to get the rules in a keyed array with the format RULE => MESSAGE, such as:

$validation = array(
    'username' => array(
      RULE_NOT_EMPTY => 'You must enter a username.',
      RULE_ALPHANUMERIC => 'You may only use letters and numbers in your username.',
      '.{6,}' => 'Your username must be at least 6 characters.'
    ),
    'password' => array(
      RULE_NOT_EMPTY => 'You must enter a password',
      RULE_ALPHANUMERIC => 'You may only use letters and numbers in your username.',
      '.{6,}' => 'You password must be at least 6 characters'
    )
  );

Don't worry if your validation scheme doesn't match, you can always write a function to convert from your format.  Notice that in addition to the shared rules, we can add a custom rule created just for a particular field.  To simplify the JavaScript and handle cross browser issues, I'll use the jQuery library.   The JavaScript part works in two pieces.  First, a static JavaScript function that will take in the form and the rules and do the validation.  This will live in an external js file, such as common.js.

function validateForm(form, rules) {
    //clear out any old errors
    $("#errors").html("");
    $("label").removeClass("error");
    
    //loop through the validation rules and check for errors
    $.each(rules, function(field) {
      $.each(this, function(rule) {
        var val = $.trim($("#" + field).val());
        var exp = new RegExp(rule);
    
        //check if the input exists and violates rule
        if ($("#" + field).attr("id") != undefined && !val.match(exp)) {
          //add the error message
          $("#errors").append("
  • " + this + "
  • "); //highlight the label $("label[for='" + field + "']").addClass("error"); } }); }); if($("#errors").html() != "") { return false; } return true; }

    The second JavaScript part is the rules for the form that is being displayed.  This code is generated by a PHP function and included directly in the html output of your page.The form described above is pretty basic.  It could look something like this:

    <form id="appFrom" action="#" method="post">
        <div>
          <label for="username">Username</label>
          <input id="username" name="username" type="text" />    
        </div>    
        <div>      
          <label for="password">Password</label>      
          <input id="password" name="password" type="password" />    
        </div>    
        <div class="submit">     
          <input name="submitForm" type="submit" value="Submit" />      
        </div>  
      </form>

    We want to apply the rules described above to this form, in a way that is unobtrusive to the original code.  First thing we'll need to do is convert the PHP rules array to JavaScript.  This can be done with the PHP function json_encode.  PHP's JSON functions are included as of version 5.2.  For older versions you can use the JSON PECL extension or code your own version of the function.

    The other critical part is to trap the submit of the form.  You could do that by altering the form tag with an onsubmit, but instead we'll use jQuery's "submit" function, which can be placed anywhere in the html.  Using JavaScript validation does not require the user to have JavaScript on their browsers.  User's without JavaScript support will simply submit the form as normal and the server will handle the validation.

    These two requirements can be encapsulated in a single PHP function.

    function clientValidation($rules, $formId) {  
        $output = array();
        $output[] = '<script type="text/javascript">';    
      
        //convert the rules array into javascript  
        if (function_exists('json_encode')) {
          $output[] = 'var rules = eval(' . json_encode($rules) . ');';
        } else {    
          exit('This code requires PHP JSON support');     
        }  
      
        //catch the form submission  
        $output[] = '$(document).ready(function() {';
        $output[] = '$("#' . $formId . '").submit( function() {'; 
        $output[] = 'return validateForm($(this), rules)';
        $output[] = '});';  
        $output[] = '});';    
        $output[] = '</script>';    
        echo implode("\n", $output);
      }

    To use the JavaScript validation on our form we only need to include the external JavaScript file and call the function above.

    clientValidation($validation, 'appFrom');

    On the client side, when the user submits the form, it is intercepted by the new validation routine.  The supplied rules are looped through, and if there was a matching input field the rules are applied.  Fields that fail a particular test have their labels assigned the CSS class "error".  The message is also added as a <li> element to the errors list. 

    At the end of the validation, if any of the fields failed, the form is stopped from being submitted.  The user can then fix the errors and attempt to re-submit, which is the same process as presented in the original scenario.  Now however the advantages are that the errors are shown instantaneously, as there is no longer any server interaction or page loads.

    We now have a powerful, re-usable client side validation system that can be included in single PHP scripts or converted for MVC frameworks. 

    This type of validation should be used in addition to server side validation.  This code does not protect against users without JavaScript support or users who are attempting malicious attacks.

    0 Comments

    Add new comment