In this article I'll show you how to build a simple, yet powerful and extensible jQuery form validation plugin
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.
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 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.
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.
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.
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.
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.
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:
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.
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.
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.
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.
As a last step, we're going to edit the Field object we defined earlier:
Now each field will validate upon the "change" event.
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.
You can look at the full jquery.validation.js code itself, feel free to use it.
<form>
<fieldset>
<legend>Legend Name</legend>
<div>
<label for="name">Name</label>
<input type="text" id="name" name="name">
</div>
</fieldset>
</form>
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”>
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.
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."
});
$.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.
*/
Take a look inside
Lets take a look at our object in the firebug console.console.log($.validation);
console.log($.validation.getRule("xhr-test"));
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;
}
var Field = function(field) {
this.field = field;
this.valid = false;
}
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);
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() {}
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() {}
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;
}
}
}
var Field = function(field) {
this.field = field;
this.valid = false;
this.attach("change"); // add this line.
}
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 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!");
}
});
});
No comments:
Post a Comment