Introduction
I was going through the jQuery validation plug-in code (Jquery.validate.js) and found it is very interesting and useful. I thought it would be beneficial if I share my findings here to those who are looking to have an understanding on how the jQuery validation framework works. This article contains the internals of how the jQuery validation plug-in works, and also explains how we can extend the default rules to provide our own validation methods.
Background
I was a bit curious about how jQuery magically validates input controls just by applying simple class rules like required email
, required url
etc., to a class attribute. So I spent a couple of hours to understand how Jqquery.validate.js was written by experts.
Using the code
To examine how the jQuery validation works, I took a simple HTML page and added a few controls on it. I also applied a few (existing) rules to see how the validation worked. The code below shows the validation.html code.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script src="jquery-1.4.1.js"></script>
<script type="text/javascript" src="jquery.validate.js"></script>
<style type="text/css">
* { font-family: Verdana; font-size: 96%; }
label { width: 10em; float: left; }
label.error { float: none; color: red; padding-left: .5em; vertical-align: top; }
p { clear: both; }
.submit { margin-left: 12em; }
em { font-weight: bold; padding-right: 1em; vertical-align: top; }
.class1 {color:red}
</style>
<script>
$.extend($.validator, { cool: function () { } });
$(document).ready(function () {
$("#commentForm").validate();
$.validator.addMethod("PinCode", function () { },
"PIN code is not in valid format");
});
</script>
</head>
<body>
<form class="cmxform" id="commentForm" method="get" action="">
<fieldset>
<legend>A simple comment form with submit validation and default messages</legend>
<p>
<label for="cname">Name</label>
<em>*</em><input id="cname" name="name"
size="25" class="class1 required" minlength="2" />
</p>
<p>
<label for="cemail">E-Mail</label>
<em>*</em><input id="cemail" name="email"
size="25" class="required email" />
</p>
<p>
<label for="curl">URL</label>
<em> </em><input id="curl" name="url"
size="25" class="required url" value="" />
</p>
<p>
<label for="ccomment">Your comment</label>
<em>*</em><textarea id="ccomment" name="comment"
cols="22" class="required"></textarea>
</p>
<p>
<input class="submit" type="submit" value="Submit"/>
</p>
</fieldset>
</form>
</body>
</html>
How it works
In the above HTML file, the classes applied were required
, required email
, required url
, etc. These minimal changes on the input elements should be good enough to make them part of the jQuery validation plug-in framework.
The heart of the jQuery validation framework is the jQuery validator ($.validator
) which contains all the necessary default settings to handle an element's validation work. the jQuery validator comes with default settings and when we call the validate()
method on the form, the validate()
method takes options
as an input parameter and returns the validator. We can always send our own options which will extend the jQuery default validation settings. Below are the default settings with which the validator comes with initially.
Here are the default settings for the validtor:
messages: {},
groups: {},
rules: {},
errorClass: "error",
validClass: "valid",
errorElement: "label",
focusInvalid: true,
errorContainer: $( [] ),
errorLabelContainer: $( [] ),
onsubmit: true,
ignore: [],
ignoreTitle: false,
onfocusin: function(element) {
this.lastActive = element;
if ( this.settings.focusCleanup && !this.blockFocusCleanup ) {
this.settings.unhighlight && this.settings.unhighlight.call(
this, element, this.settings.errorClass, this.settings.validClass );
this.errorsFor(element).hide();
}
},
onfocusout: function(element) {
if ( !this.checkable(element) &&
(element.name in this.submitted || !this.optional(element)) ) {
this.element(element);
}
},
onkeyup: function(element) {
if ( element.name in this.submitted || element == this.lastElement ) {
this.element(element);
}
},
onclick: function(element) {
if ( element.name in this.submitted )
this.element(element);
else if (element.parentNode.name in this.submitted)
this.element(element.parentNode)
},
highlight: function( element, errorClass, validClass ) {
$(element).addClass(errorClass).removeClass(validClass);
},
unhighlight: function( element, errorClass, validClass ) {
$(element).removeClass(errorClass).addClass(validClass);
}
We can override any of these settings on our local page. For example, take the event onfocusout
; I want to perform a different logic other than what the framework provides. In our local page, we can do some thing like below:
$.validator.defaults.onfocusout = function(element)
{
},
The label will be used to display error messages; 'error' has its class, and we can override this class in our application and provide our own custom style, somewhat similar to what we have done in the above sample.
label { width: 10em; float: left; }
label.error { float: none; color: red; padding-left: .5em; vertical-align: top; }
How rules are applied and how the validation takes place
The jQuery validator has a rules
object ($.validator.settings.rules
). A rule in jQuery associates an element to a validation method, and a method defines the validation process of how an element needs to be validated. The framework rules are categorized into four levels:
- class level
- attribute level
- meta rules
- static rules
For example, here are some of the classRuleSettings
defined in the framework:
classRuleSettings: {
required: {required: true},
email: {email: true},
url: {url: true},
date: {date: true},
dateISO: {dateISO: true},
dateDE: {dateDE: true},
number: {number: true},
numberDE: {numberDE: true},
digits: {digits: true},
creditcard: {creditcard: true}
},
Rules preparation for an element
To get the rules applied on an element, jQuery first gets the value of the class attribute; for example, consider our sample email element:
- It has "
required email
" as class value - It splits the value returned by the class attribute using the delimiter ' '; in our case, we get two values:
required
, email
. - For each value (
required
, email
), it gets the mapping from classRuleSettings
and prepares the validation rule for the element. Here the validation rule is {required:true,email:true}
. For each value in the rule like required
, email
etc., the framework has methods defined in $.validator.Methods
. A list of methods describes how an element needs to be validated based on the rules assigned to it. Here are the some of the framework validator methods: MinLength
, MaxLength
, rangelength
, min
, max
, range
, url
, date
, dateISO
, number
, digits
, creditcard
, etc.
- After class level rules are created, it iterates through other attributes of the input element (like
minlength
, maxlength
) etc., and prepares rule objects like above {required:true, minlength=2}
and combines both the rules. It works in a similar way for meta, static rules also. - Once the rules preparation is done, it goes through the validation methods (
$.validator.Methods
) list and calls the appropriate method based on the rule association. In the above case, three validation methods will be called: required
, email
, minlength
, (<input name="cname" id="cname" class="required email" minlength=2 />
). Here is how the code for the methods look like in the validation framework:
$.validator.Methods{
required: function(value, element, param) {
if ( !this.depend(param, element) )
return "dependency-mismatch";
switch( element.nodeName.toLowerCase() ) {
case 'select':
var val = $(element).val();
return val && val.length > 0;
case 'input':
if ( this.checkable(element) )
return this.getLength(value, element) > 0;
default:
return $.trim(value).length > 0;
}
},
email: function(value, element) {
return this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-
\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+
(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\u
FDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+
)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|
[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x
7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x
0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-
\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-
\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\u
FFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+((
[a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\u
D7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\u
F900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\u
FDCF\uFDF0-\uFFEF])))\.?$/i.test(value);
},
}
What if the framework provided validation methods are not sufficient
How do we add our own validation mechanisms/methods? Well, the framework provides a method called $.validator.addMethod(name, method, Message)
which adds a new validation method to the Methods
list. Here, name
is the name of the validation method, method
is the validation process, and Message
is the validation failure message. In the above sample, I have added a pin code validation method which is not part of the framework. For simplicity, the method always returns false to kick the validation, and the validation message will be displayed.
The framework has default message settings for each kind of validation failure; for example, when the required validation fails, it displays "This field is required" in the example below:
messages: {
required: "This field is required.",
remote: "Please fix this field.",
email: "Please enter a valid email address.",
url: "Please enter a valid URL.",
date: "Please enter a valid date.",
dateISO: "Please enter a valid date (ISO).",
number: "Please enter a valid number.",
digits: "Please enter only digits.",
creditcard: "Please enter a valid credit card number.",
equalTo: "Please enter the same value again.",
accept: "Please enter a value with a valid extension.",
maxlength: $.validator.format("Please enter no more than {0} characters."),
minlength: $.validator.format("Please enter at least {0} characters."),
rangelength: $.validator.format(
"Please enter a value between {0} and {1} characters long."),
range: $.validator.format("Please enter a value between {0} and {1}."),
max: $.validator.format("Please enter a value less than or equal to {0}."),
min: $.validator.format("Please enter a value greater than or equal to {0}.")
},
If we want to display our own custom messages then here is how we can override every message in our local web application:
$.validator.messages.required ="My Custom message";
When we click on the Submit button, the code below picks up:
this.submit( function( event ) {
if ( validator.settings.debug )
event.preventDefault();
function handle() {
if ( validator.settings.submitHandler ) {
if (validator.submitButton) {
var hidden = $("<input type="hidden" />").attr(
"name", validator.submitButton.name).val(
validator.submitButton.value).appendTo(validator.currentForm);
}
validator.settings.submitHandler.call( validator, validator.currentForm );
if (validator.submitButton) {
hidden.remove();
}
return false;
}
return true;
}
if ( validator.cancelSubmit ) {
validator.cancelSubmit = false;
return handle();
}
if ( validator.form() ) {
if ( validator.pendingRequest ) {
validator.formSubmitted = true;
return false;
}
return handle();
} else {
validator.focusInvalid();
return false;
}
}
In the above code, validator.form()
does the form validation element by element and accumulates the error list, and finally the $.validator.ShowErrors()
method will display the errors on the page.
How we do conditional validation
Remember while calling the framework validate()
(which returns the validator), we can pass in options as an input parameter. Here I have written a dependency function handler to do a conditional required check. Here I am applying a condition validation on the URL, which says the URL is required only when the value in email is empty (of course, it doesn't make sense to have this kind of a dependency, but this is just to demo the options input parameter).
$("#commentForm").validate(
{
rules:
{
url:
{
required: function () {
return $("#cemail").val().length === 0;
}
}
}
}
);
In the above code, the URL is required only when email is empty; internally, jQuery uses extend()
to extend our own custom provided rules with the default settings of the validator. Shown below is how the code settings are extended with our default options sent as input parameters:
$.validator = function( options, form ) {
this.settings = $.extend( {}, $.validator.defaults, options );
this.currentForm = form;
this.init();
};
Points of interest
- The jQuery validation framework hides a lot of complex logic and gives the user flexibility to specify rules in a simple manner.
- It is highly extensible, we can override existing ones or add new rules/validation methods.
- We can have individual teams write their own validation methods and hook them into the existing framework, that is how we can separate the concerns.
- Error messages can be customized local to a page.
Disclaimer
This article uses a sample which is not for direct production deployment. This is to give the reader an idea of how jQuery works and how we can use it in our local web applications. It is the individual's responsibility to follow the necessary best practices while implementing Model Binding.