Tips for using the jQuery Validation Plugin

Posted on Jul 5, 2013 in Blog

The jQuery Validation plugin is a useful tool for client side validation of forms, and while there’s a fair bit of documentation and examples, I found them to be somewhat confusing to navigate. As is often the case, I found myself searching StackOverflow and getting lost in the documentation putting together the pieces I needed, such as validating behind the scenes, dealing with hidden fields or fields created on the fly, and playing nice with other plugins.  The great news is that, while it might not be obvious from the documentation, the validation plugin makes all of those situations really easy to handle. I don’t know that this post presents any information that can’t be found in the doc or someplace else, but I hope collecting all these pieces together in one place makes it easier the next time I or someone else needs to get the plugin quickly integrated with a project.

 (Note: the examples in this post are mostly in CoffeeScript and were pulled out of a Backbone.js project) They’ve been copiously edited, assume some knowledge of jQuery, JavaScript, etc. and are not intended to be fully fleshed out code examples.

Getting started with the plugin

Understanding the difference between validate() vs. valid()

I’m not going to cover basics of setting up the plugin here, there is more than one way to do it and the website gives a number of useful examples. But I did want to point out one issue that tripped me up , and judging from questions posted on StackOverflow, I’m not the only one who’s been sidetracked by this.  The plugin documentation says that the validate() method “Validates the selected form”, but that statement is misleading. The validate() method is used to initialize the plugin on the form, making the form an object that can be validated.  (Thanks to Sparky over at SO for this illuminating answer. ) The form is not actually validated when validate() is called, validation will occur later as the user is entering data, when the submit button is clicked, or when the valid() method is called.

If you need to validate behind the scenes this is an important difference to understand. Many examples of using the plugin assume that the form validation will occur when the user clicks the submit button, the plugin does that automatically and you could use the plugin without even knowing about the valid() method. But not all form submissions occur with the click of a submit button. For example, in our project we do some data massaging from the form fields and ultimately call a .save() method on a Backbone object.  Call validate() on your form when the DOM is ready, this will give your users real time validation as they are entering data. Then if you are doing behind the scenes validation, for example prior to an Ajax submission, call valid() when you are ready to validate the form.

Initializing the plugin, using validate():

  $("#ei-report-edit").validate({ 
    ignore:[], 
    rules: 
      ... 
    errorPlacement: (error, element) -> 
      ... 
    invalidHandler: (event, validator) -> 
      ...

Later on validate the form, using valid():

  if !($ 'form#ei-report-edit').valid() 
    #handle errors 
  else 
    #carry on with the form submission

HowTo: Create rules based on CSS classes

Many examples assume you know the ids of all the fields in your form and can just map rules to individual fields. But the fields in our form are often created on the fly. Making sure a required field was set was easy, since the plugin will utilize the required attribute on the form element (assuming HTML5 broswer support of the required attribute). To assign other validation rules you can call the addClassRules() method. For example, the following ensures that any field with a class of ‘isoDate’ is confirmed to be a valid ISO date:

$.validator.addClassRules("isoDate", {dateISO:true})

Note, I had hoped I could select the fields based on a data attribute and not have to add a class just for validation:

$.validator.addClassRules("input[data-role='datebox']", {dateISO:true})

but I couldn’t get it to work. It’s quite possible it does work and I just overlooked something.

Integrating with other UI plugins, such as jQueryMobile or Chosen

Our project uses several plugins that modify the style of the form: jQuery Mobile, the Chosen plugin for styling select boxes, and jQM DateBox as a date picker. Because these plugins all take the original form inputs, style them with extra HTML elements and sometimes even hide the actual input element, it can mean extra work integrating the validation plugin.

HowTo: Override default error message placement

By default the validation plugin will insert an error message in a label after the invalid element. Fortunately it’s relatively easy to override this by passing a custom error placement callback in the initialization of the plugin. In this example we override the error placement for dates, select boxes, and radio controls.

  $("#ei-report-edit").validate({ 
    ... 
    errorPlacement: (error, element) -> 
      $element = $(element) 
      if $element.is("input[data-role='datebox']") 
        error.insertAfter($element.parent("div")) 
      else if $element.is(".chzn-select") 
        error.insertAfter($element.next(".chzn-container")) 
      else if $element.parents().is(".ui-controlgroup-controls") 
        error.insertAfter($element.parents(".ui-controlgroup-controls"))
      else 
        error.insertAfter(element)

(Tip: Figuring out where to place the error label can take some trial and error, this is one place where being able to call the valid() method really comes in handy, since you can call it repeatedly using the console. I would place a breakpoint in my errorPlacement handler, call valid() and play around with inserting the error message in various places in the DOM until I got it right.)

HowTo: Validate some but not all hidden fields

As I mentioned above, some of our plugins hide the actual input field we need to validate. Setting up the plugin to validate hidden fields is pretty easy, you pass an empty array in the ignore option of validate(): 

  $("#ei-report-edit").validate({ 
    ignore:[], ...

Alas, this may open up it’s own can of worms, in our case we had some inputs that were displayed (and required) conditionally, i.e. whether the form fields were displayed depended on the value of another field in the form. My first solution was to add a ‘required’ attribute to the conditional field when it was displayed and remove it when it was hidden:

  #show the conditional fields 
  $myWrapperDiv.show('slow') 
  #set any input fields to required 
  $myWrapperDiv.find("input").attr("required", "required")

and later,

  $myWrapperDiv.hide('slow') 
  #set any input fields to required 
  $myWrapperDiv.find("input").removeAttr("required")

I just wasn’t sure about this though. Sooner or later I would forget to remove the required attribute when I hid the element and then the user would be stuck with a form they couldn’t validate because of a hidden required field someplace. In the end I decided to add a class to the input fields and a custom rule to the validator:

the form field:

  <input id="my_maybe_required_field" type='text' ... />

tell the validator to validate any fields with the class ‘requireWhenShown’ using my rule:

  #$.validator.addClassRules(<className>, {<methodName>:true})  
  $.validator.addClassRules("requireWhenShown", {requireWhenShown:true})

The validation rule is pretty simple, it just confirms that the element is visible and if so passes it on to the default validation for required fields:

  $.validator.addMethod("requireWhenShown", (value, element) -> 
    if $(element).is(":visible") 
      return $.validator.methods.required.call(@, value, element) 
    else return true 
  , "This field is required")

HowTo: Revalidate a hidden field that is set by another field

This is another problem I encountered due to the other plugins we were using, specifically the Chosen plugin. When the Chosen plugin works its magic, our required <select> is hidden and then updated by the plugin. Setting the ‘ignore’ option in validate() made sure our field was validated, but if there was an error and the user corrected it, the error message didn’t go away until the entire form was revalidated. Once again jQuery validation plugin had a solution, we simply needed to listen for a change event on the chosen objects and then manually validate the hidden field:

  #Setting up the events in Backbone, in this case handling changes to fields 
  #created by jQuery Mobile, Chosen, or jQm Datebox: 
  events: { 
    'change .chzn-select': 'validateElement', 
    'change .type_of_monitoring input[type=radio]': 'validateElement', 
    'change input[data-role=datebox]': 'validateElement', 
    'change .inspection_type': 'validateElement' 
   }

  #tell the plugin to validate the field
  validateElement: (event) -> 
    @.validator.element("#" + event.currentTarget.id)

If the user has corrected an error, the error message next to the element will now go away.

HowTo: Do something after the form is validated, such as displaying a list of all the errors

When the form is validated our app shows the errors inline next to the invalid fields. But our form is long and the field that failed may not be in view of the user. So we also show a list of errors above (as well as below) the form. To show this list, we again return to the plugin initialization, including an invalidHandler callback in the options to validate().

  $("#ei-report-edit").validate({
    ... 
    invalidHandler: (event, validator) -> 
      numErrors = validator.numberOfInvalids() 
      if (numErrors) 
        if numErrors == 1 
          message = "The following error was found, " 
        else 
          message = "The following errors were found, " 
          message += " please fix and save again." 
      $errList = ($ '.save-errors ul') 
      $errList.empty() 
      
      $.each validator.errorList,(key,val) -> 
        e = val.element 
        #special handling of radio control groups so that we get the label 
        #for the group and not for the actual element. 
        $e = $(e) 
        $parents = $($e.parents(".ui-controlgroup-controls")).first() 
        if $parents.length > 0 
          label = $($parents.prev(".ui-controlgroup-label")).text() 
        else 
          #find the text of the label for this element 
          label = $($("label[for='" + e.id + "']")[0]).text() 
          #trim the required "*" off of the displayed label for the message 
          if (label.lastIndexOf("*")) > -1 
            label = label.slice(0, label.lastIndexOf("*")).trim() 
         
        $errList.append('<li>' + label + ": " + val.message + '</li>') 
        ($ '.save-errors .message', @.el).text message 
        ...

Our handler creates a list of form labels and error messages. The plugin methods and properties that are useful here are validator.numberOfInvalids() as well as validator.errorList, which returns the error message and element for each error. I needed the element so I could traverse back up to the label, but if you don’t need to do that validator.errorMap is also handy, where the keys are the element names and the values the error messages.

Hope these tips help you make the most of the jQuery Validation plugin !