Article: Advanced jQuery form validation »
FERDY CHRISTANT - DEC 4, 2008 (02:11:28 PM)
One of the most common tasks of a (web) developer is to implement validation for forms. This is needed for pretty much any form, as you simply cannot trust user input. Much has been written and said about input validation already, so what is it that makes this article worthwhile for me to write and for you to read?
I will focus mostly on front-end validation and dive into back-end validation where needed. Much of this article can be used for any back-end, including PHP, J2EE, .NET and Lotus Notes.
Introducing the form
As an example for this article, we'll be looking at the Jungle Dragon registration form, which is used to sign up new users:
Don't mind the basic look & feel, this form is not styled yet. It contains a number of fields and each field has some validation rules applied to it:
- Username: Is required. Needs to be of a minimum length and cannot exceed a maximum length. Needs to be unique (in the back-end), can contain Unicode characters, except for a few characters on a blacklist.
- Email: Is required. Needs to be in a valid email format. Must be unique in the back-end. Cannot exceed a maximum length.
- Confirmed email: Is required and needs to exactly match the first email field.
- Password: Is required, needs to be of a minimum length and cannot exceed a maximum length.
- Confirmed password: Is required and needs to exactly match the first password field.
- Agreement: Needs to be checked in order to accept the terms and conditions of the service.
- Captcha: This form contains a custom Captcha check that needs to be passed.In another article, Building your own Captcha, you can read all about how to build one.
The form markup
I will assume that you know how to build a web form, so I will only focus on the parts that matter. Let's start with the form open tag:<form action="user/register.html" method="post" accept-charset="UTF-8" id="frmRegister">
The action attribute needs to point to your back-end script that handles the processing of the form. The method attribute indicates that we will be posting the input fields to that back-end script. The accept-charset attributes is set to UTF-8, so that we can accept Unicode text. Finally, the form ID attribute is crucial, as we will be referring to that from jQuery.
Next, let's have a look at the markup for a single field, the username field:
1. <label for="username">Choose a username</label>
2. <input type="text" name="username" id="username" value="" />
3. <label for="username" generated="true" class="error"></label>
The first line creates the label that displays the name of the field. The second line renders the actual input field. It is absolutely crucial to both name and id the field. The third and last line is an additional label that will be placed below the field as a placeholders for field-specific validation error messages. The markup of the other fields on the form work exactly like this field, so I will not go through all of them.
Finally, the markup for two buttons: the Join button and the Cancel button:
<input type="submit" value="join" />
<input type="submit" value="cancel" class="cancel" />
All standard HTML, right? Correct. There's only one thing worth mentioning. The cancel button has the "cancel" class assigned. This is a special class used by the jQuery validator plugin in order to stop validation.
Setting up jQuery and the validation plugin
We're setup to do some coding. Let the games begin.
The front-end validation code
Without further delay, I'm going to simply throw the full code at you and explain it line by line. Since the code listing is quite long, I have placed it on a seperate page. The page will open in a new window so that you can conveniently keep it next to the article. Click here to open the code listing. Below are the most important line numbers and their explanation:
1. This is the ready call of jQuery itself. All jQuery code must be placed inside this event, including our validation code.
7. This is our main call to the validation plugin's validate method. As you can see, we are using the id of the form we set earlier. The lines that follow, which is the majority of the code, indicate the various options of the validate method.
8. By default, validation routines run at every keypress, which I think is a bit over the top, especially considering we will be doing remote calls to the back-end. By setting onkeyup to false, validation only takes place upon a field exit (onblur) and upon a submit (user hitting ENTER or clicking the submit button).
9. This is the start of the rules literal, the most important part of the code. The rules literal consist of field elements at the highest level, and validation rules at the next level.
10-13. Here we are setting some validation rules for the username field. Note that the required, minlength and maxlength rules are built-in validation routines of the plugin. There are dozens of these rules available for you to use, here is the list.
14-15. These two lines are very important. They are custom validation routines. It is very easy to extend the validation plugin with your own validation rules, such as checking for illegal characters or checking if a username is unique in the back-end. Here we are simply declaring these custom rules, we will get to the implementation of them later on. Note that the order of the validation rules is also important. We want to do a char check before we will even consider doing any remote checks!
17-39. The rest of the validation rules follow the same principles as discussed. You declare the fieldname and then set the rules. Note how we have set the email field to require a valid email address (email:true) and how we are defining rules to match fields (equalTo: "email_first").
40. This is the start of the messages literal. The messages literal is used to explicitly set validation error messages per fieldname/rule combination. When validation does not pass, these individual error messages will be placed below the fields (or wherever you have positioned them in your markup). Note that these messages are optional, the plugin has built-in messages that may do just fine. I am a fan of explicit programming, so I decided to set these messages by hand.
41-70. These are the actual validation error messages. As you can see, they are organized per field and then by rule. You can simply set a static string, or if you need to include dynamic data, you can use the jQuery.format function, where %s will be replaced at runtime with the parameter(s) set in the validation rule.
Halfway down the code, let's take a small break. We have witnessed already how powerful the jQuery validation plugin is. It takes vey little code to implement complex validation rules. However, it cannot cope with every possible validation rule. Luckily, we can easily write our own. For the registration form, we will be needing three custom routines:
- Username check. We want to check if the username entered does not yet exist in the database.
- Email check. We want to check if the email entered does not yet exist in the database.
- Char check. We want to check the username for illegal characters, yet still allow Unicode.
The way to do this is to use the addmethod method of the jQuery validator. Let us continue with the code:
77. Here we are defining the custom usernameCheck method. Note how we set this rule earlier for the username field at line 15. The username that is being validation will be passed to the argument "username" of our method definition.
78. For the username check, we will be doing a Ajax call to a server-side method that tells us whether the username is already in use. This line sets the URL we will use for this. It is worth mentioning that the validation plugin has a built-in "remote" rule, which means that for simple remote checks, we would not even need to write this custom method. We will do this anyway, as it gives us more control and we can learn a lot about how jQuery (not the validation plugin) works.
79. This line starts jQuery's ajax method. This is a low level method that offers the flexibility that we need. The lines that follow indicate the options that we pass to this method.
80: We do not want our remote calls to be cached, because the username check is data-driven and data may change at anytime. Therefore, we set caching to false.
81. Whenever we do a remote call, we can choose to make an asynchronous call (do not wait for the result of the remote check), or a synchronous call (wait for the result of the remote check). Because of the way the validation plugin works, we need to explicitly set it to synchronous, by setting asynchronous to false, otherwise things will not work.
82. For remote calls we need to choose whether to do a GET or POST request to the remote URL. I am setting it to "post" for two reasons. First, in a GET request we would have to pass the username via the URL. Since it can contain illegal URL characters (any Unicode) we would have to deal with encoding and decoding data in the URL. Debugging would become harder too. The second reason is personal. My back-end is Code Igniter, a PHP framework that does not accept URL parameters via query strings in GET requests. By doing a POST, we avoid both issues.
83. Here we are setting what data to POST. In our case it is simply the username that was passed to the custom validation function.
84. The URL to post to.
85-89. We will expect our method to return the result of the check in JSON format. For this case, it means it will simply output "TRUE" or "FALSE", so we will simply return that as part of the function.
90. The last parameter of the custom validation function definition we have left empty (''). If you want to, you can set the custom validation error message here. Since we have explicitly defined this in the messages literal already, it is empty here.
92-105. This blocks checks if the email address that was entered by the user is unique in the database. It work in a similar manner as our username check function.
108-121. Finally, on to our third and last custom validation function. We want to prevent users from entering illegal characters for the username field. The simple approach is to use an alphanumeric rule. This essentially covers the range [a-z], [A-Z], and a few additional characters. The problem with this approach is that marks any Unicode character outside the Latin1 range as illegal, which is not what we want. Instead, we will want to allow all Unicode characters, expect for a few illegal characters on a blacklist.
This completes our front-end validation code. The good news is that this code can easily be integrated into any back-end. Whilst we have mostly focused on getting things to work, there is a wealth of additional power and options available in jQuery and the validation plugin to further enrich our form. Now, on to the bad news.
The purpose of the code discussed in the first part of this article is to make validation and its feedback rich and fast, by preventing page refreshes. It does not replace back-end validation however:
- Spammers may post to your forms using a script, completely bypassing your front-end validation.
Unfortunately, this means that we pretty much have to duplicate the validation rules in our back-end. If you would add database constraints into the mix, there are in fact three layers at which we are doing the validation. Since this article was written with any platform in mind, I can only give you some general clues on the things that matter in the back-end:
Our back-end, which is the URL we are posting to, will receive the form values from the request and has to run the same validation rules as the front-end validation. In an ideal situation, both the front-end and back-end can share the validation rule and message declarations, but this greatly depends on your platform and back-end validation library. Otherwise, you will have to duplicate the rules and messages, or you have to build some glue code.
As said, the implementation of the back-end validation differs per platform. For the sake of example, here is how I am doing it in PHP, using Code Igniter's FormValidation library:
// setup validation rules
$this->form_validation->set_rules('username', 'username', 'trim|required|min_length|max_length|
$this->form_validation->set_rules('email_first', 'email address', 'trim|required|max_length|
$this->form_validation->set_rules('email_second', 'confirmed email address', 'trim|required|max_length|valid_email|
$this->form_validation->set_rules('password_first', 'password', 'required|min_length|max_length');
$this->form_validation->set_rules('password_second', 'confirmed password', 'required|min_length|
$this->form_validation->set_rules('tos', 'in order to join, agreeing to the Terms and Conditions', 'required');
// run the validator
if ($this->form_validation->run() == false)
// error handling code
As you can see, the way rules are defined is similar to the front-end code. Validation messages are defined in a language file however.
On to the next part: user feedback. Assuming that we have the back-end validation working, we need to provide the user with feedback. This consists of two parts: field preservation and error messages. In the front-end validation scenario, all values entered by the user are preserved upon validation, because the submit to the server has not really happened yet. In the back-end scenario, we need to explicitly capture the values that the user has entered and pass it back to the form when we reload it, but only if validation has failed. Typically this is an easy thing to do: we pass the post variables back to the form, and change the form markup so that the value attributes of the input fields get these values assigned. Here's a PHP example:
<input type="text" name="username" id="username" value="<?= $username; ?>" />
The second part concerns error messages. In the front-end validation, we positioned field-specific error messages using labels in the markup. We will want the messages that are part of the result of the back-end validation to use these same labels. In most cases, this will me a matter of passing the field errors to the markup and filling the label with the specific message:
<label for="username" generated="true" class="error"><?php echo form_error('username'); ?></label>
With this in place, we now have a consistent user experience: the rules, messages and styling are exactly the same in the front-end and the back-end. Now, let's have a look at a few more things to make it more robust.
First, the back-end validation code will receive its parameters via POST variables. You cannot trust these blindly, you will need to santize these variables to prevent XSS attacks, SQL injection and all those other goodies of the web. My only advise here is to find a good library that does this for you. In my case, the Code Igniter framework takes care of this:
By accessing the post variable this way, it is automatically sanitized.
Now, let's have a word about the remote checks we used in the front-end validation code. They were used to check if a username or email address entered by the user was unique in the database. In our back-end logic, we write the database check methods only once. For back-end validation, we call it directly. For front-end validation, we can provide a JSON wrapper method that calls the database check. Why the additional wrapper method? For the database check method we will want true/false returned, whilst the remote calls expect a JSON response, which means printing (not returning!) "true" or "false" to the HTTP response.
As if life is not complex enough already, a word of warning applies to these database check methods. The email checker, for example, is called remotely to check if an email address exists in the database. It will return "TRUE" if this is the case. A brute force remote dictionary attack may reveal a lot of confirmed email addresses to spammers, so be careful. It goes too far for this article to explain how to close this leak, but I can tell you this: don't solve it in your application, solve it at the web server level. There, you can configure IP blacklists, set request tresholds, etc. Coding these things in our application introduces unwanted complexities and a maintenance nightmare.
I hope this article helps you in building advanced, robust, and consistent forms. As you can see, even with the best of libraries, it is still a lot of work. Writing this article was a lot of work as well, so please provide your feedback and rating below :)