DotNetSlackers: ASP.NET News for lazy Developers

Wednesday, August 3, 2011

In this article I'll show you how to build a simple, yet powerful and extensible jQuery form validation plugin
<form>
    <fieldset>
        <legend>Legend Name</legend>
        <div>
            <label for="name">Name</label>
            <input type="text" id="name" name="name">
        </div>
    </fieldset>
</form>
This is how I markup my forms. Trying to keep it as simple and semantic as possible, but adding the div to make styling easier.

Specifying input validation

In HTML4 there is no obvious way attach validation rules to an input. A few developers have been somewhat orthodox and added their own “validation” attribute.
<input type="text" id="name" name="name" validation=required email>
This is not valid HTML, but it works in all browsers, and maybe it’s better than using a valid attribute for the wrong reasons. It's up to you, but this is how I do it.

The validation object

The base validation object contains a set of methods and properties that only needs be stored in one place, but can be accessed globally. In object oriented terminology this is commonly referred to as a Singleton.
// Wrap code in an self-executing anonymous function and 
// pass jQuery into it so we can use the "$" shortcut without 
// causing potential conflicts with already existing functions.
(function($) {

    var validation = function() {

        var rules = {  // Private object

            email : {
               check: function(value) {

                   if(value) {
                       return testPattern(value,"[a-z0-9!#$%&'*+/=?^_`{|}~-]+
                                                   (?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)
                                                   *@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])
?\                                                 .)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])");
                   }
                   return true;
               },
               msg : "Enter a valid e-mail address."
            },
            required : {

               check: function(value) {

                   if(value) {
                       return true;
                   }
                   else {
                       return false;
                   }
               },
               msg : "This field is required."
            }
        }
        var testPattern = function(value, pattern) {   // Private Method

            var regExp = new RegExp(pattern,"");
            return regExp.test(value);
        }
        return { // Public methods

            addRule : function(name, rule) {

                rules[name] = rule;
            },
            getRule : function(name) {

                return rules[name];
            }
        }
    }
    //A new instance of our object in the jQuery namespace.
    $.validation = new validation();
})(jQuery); 
// Again, we're passing jQuery into the function 
// so we can use $ without potential conflicts.
The object contains a set of rules, each with a function and a message. There's also a function called "testPattern". Being defined with the var keyword makes them private and inaccessible outside the object itself.
NOTE: I've simplified the regular expressions above to make the example readable. I suggest you take a look at the expressions from the official jQuery valide plugin for the real deal.
NOTE2: Never rely on JavaScript validation alone if your sending data to a server, you'll have to do server side validation too.
Adding the object to the jQuery $ namespace is useful because now we can access it like any other jQuery object, hence, it'll be helpful later on in our plugin code.

Public methods

Instead of hard coding lots of rules, we keep it down to a minimum. We defined a method called "addRule" and made it public by wrapping it in a return statement. The concept of returning methods and properties to make them public is commonly referred to as the module pattern.

Adding additional validation rules

People can now use our method to quickly add new custom validation rules without having to modify our code.
$.validation.addRule("test",{
    check: function(value) {
        if(value != "test") {
            return false;
        }
        return true;
    },
    msg : "Must equal to the word test."
});
We’ve now added a custom validation rule called “test”, maybe not so useful, but consider the next example. We add a rule to check whether a username is taken or not, with an imaginary AJAX request.
$.validation.addRule("xhr-test", {
    check: function(value) {
        $.get("/url-to-username-check", { username: value }, function(data) {
            if(data.isValid) {
                return true;
            }
            return false;
        });
    },
    msg : "Username already exists."
});
/*
Obviously, you’d want to improve the above 
example to handle possible AJAX failures.
*/
Some people will by now probably point out that the addRule method is essentially the same as making the object public and letting people add stuff to it themselves. Yes, that's true, but this a VERY simple abstraction. Should the code get any more advanced an abstraction like this could be helpful for anyone using your code.

Take a look inside

Lets take a look at our object in the firebug console.
console.log($.validation);
It should simply print out "object". If you click it you'll see the public methods we added earlier on. Let's see what the getRule method produces.
console.log($.validation.getRule("xhr-test"));
As you'll see, it returns the rule we added earlier. In OOP terminology we've now implemented getters and setters. Instead of allowing direct access to an objects private properties. It's better to have public methods that make these changes. It produces a cleaner API to the end user and it gives us more flexibility in case something else needs to run when modifying a property.

But someone told me the module pattern sucks...

Although I prefer this style of coding, it should be said that some developers neglect the idea of hiding properties and methods (a.k.a data encapsulation) in JavaScript. I think there are some very legitimate arguments linked to that statement. I leave it up to you to make up your own mind, nevertheless it's good to have knowledge of different design patterns even if you don't use them.

The form object

The form object will represent an instance of a form in the DOM.
var Form = function(form) {

    var fields = [];
    // Get all input elements in form
    $(form[0].elements).each(function() {
        var field = $(this);
        // We're only interested in fields with a validation attribute
        if(field.attr('validation') !== undefined) {
            fields.push(new Field(field));
        }
    });
    this.fields = fields;
}
A form has a number of fields belonging to it. We iterate over all the inputs and textareas that has a validation attribute using the jQuery each method. For each field there is, we create a new instance of the Field object which looks like this:
var Field = function(field) {
    this.field = field;
    this.valid = false;
}
We've defined some properties using the this keyword which refers to the object itself. We can now create any number of "form" instances with the new keyword.
var comment_form = new Form($("#comment_form"));
//another instance
var contact_form = new Form($("#contact_form"));
//Take a look inside the object
console.log(comment_form);
Providing your forms actually contain some fields with a validation attribute, you should see some interesting results when printing the object in the firebug console.

The JavaScript prototype object

This may seem like a tricky concept at first, but once you can wrap your head around prototypal inheritance, it's a really powerful and useful feature of the JavaScript language.

The problem

We have a large number of instances of the Field object, all of which we need to add some methods to. At first it may seem like a perfectly good idea to do the following:
Field.validate = function() {}
But this essentially means that if we have 30 fields instances, we will have 30 instances of the validate function, which is really a waste, since they all do the same thing.

The solution

Adding the validate method to the prototype object of Field will make all instances of Field inherit the validate method, but the validate method only exists in one place, so any changes to it will be reflected upon all instances of the Field object.
field.prototype.validate = function() {}
This feature should be used with care, especially when used on native javascript objects, before you fiddle around with it to much I recommend you read up further on the subject.

The Field validation methods

These are thetwo methods attached to the Field prototype object.
Field.prototype = {
    // Method used to attach different type of events to
    // the field object.
    attach : function(event) {

        var obj = this;
        if(event == "change") {
            obj.field.bind("change",function() {
                return obj.validate();
            });
        }
        if(event == "keyup") {
            obj.field.bind("keyup",function(e) {
                return obj.validate();
            });
        }
    },

    // Method that runs validation on a field
    validate : function() {

        // Create an internal reference to the Field object. 
        var obj = this, 
            // The actual input, textarea in the object
            field = obj.field, 
            errorClass = "errorlist", 
            errorlist = $(document.createElement("ul")).addClass(errorClass),
            // A field can have multiple values to the validation
            // attribute, seprated by spaces.
            types = field.attr("validation").split(" "), 
            container = field.parent(),
            errors = []; 

        // If there is an errorlist already present
        // remove it before performing additional validation
        field.next(".errorlist").remove();

        // Iterate over validation types
        for (var type in types) {

            // Get the rule from our Validation object.
            var rule = $.Validation.getRule(types[type]);
            if(!rule.check(field.val())) {

                container.addClass("error");
                errors.push(rule.msg);
            }
        }
        // If there is one ore more errors
        if(errors.length) {

            // Remove existing event handler
            obj.field.unbind("keyup")
            // Attach the keyup event to the field because now
            // we want to let the user know as soon as she has
            // corrected the error
            obj.attach("keyup");

            // Empty existing errors, if any.
            field.after(errorlist.empty());
            for(error in errors) {

                errorlist.append("<li>"+ errors[error] +"</li>");        
            }
            obj.valid = false;
        } 
        // No errors
        else {
            errorlist.remove();
            container.removeClass("error");
            obj.valid = true;
        }
    }
}
As a last step, we're going to edit the Field object we defined earlier:
var Field = function(field) {

    this.field = field;
    this.valid = false;
    this.attach("change"); // add this line.
}
Now each field will validate upon the "change" event.

Finishing the Form Object

Now our Field object is pretty much complete, let's add a few more features to the Form object before moving on to the actual jQuery plugin implementation.
Form.prototype = {
    validate : function() {

        for(field in this.fields) {

            this.fields[field].validate();
        }
    },
    isValid : function() {

        for(field in this.fields) {

            if(!this.fields[field].valid) {

                // Focus the first field that contains
                // an error to let user fix it. 
                this.fields[field].field.focus();

                // As soon as one field is invalid
                // we can return false right away.
                return false;
            }
        }
        return true;
    }
}

The jQuery plugin methods

We're going to use the jQuery extend method to make our plugin accessible as methods on any jQuery object.
$.extend($.fn, {

    validation : function() {

        var validator = new Form($(this));
        $.data($(this)[0], 'validator', validator);

        $(this).bind("submit", function(e) {

            validator.validate();
            if(!validator.isValid()) {

                e.preventDefault();
            }
        });
    },
    validate : function() {

        var validator = $.data($(this)[0], 'validator');
        validator.validate();
        return validator.isValid();
    }
});
The validation method is what we use to create a new validation instance associated to a form. You can see that we're creating a new form instance and also binding the a submit event handler to run the validation upon form submission.

The jQuery $.data method

This is pretty nifty feature and allows us to store data assoicated to a jQuery object. In this case, we are storing an instance of the form object inside the object passed into the plugin. In the validate method, that we use to validate the form at anytime, we can now call the already existing form instance, instead of creating a new one.

Usage

Based on the plugin that we've now built, here's some usage examples.
$(function(){ // jQuery DOM ready function.

    var myForm = $("#my_form");

    myForm.validation();

    // We can check if the form is valid on
    // demand, using our validate function.
    $("#test").click(function() {

        if(!myForm.validate()) {

            alert("oh noes.. error!");
        }
    });
});
 
You can look at the full jquery.validation.js code itself, feel free to use it.

That's all

I hope you found this article useful and educational, the example plugin is far from the perfect form validation script but that was not the primary goal of this article, but rather write nicely structured JavaScript code in an object oriented approach.

No comments:

Post a Comment