How the unobtrusive jQuery validate plugin works internally in Asp.net MVC

August 27, 2012

this is part of “understanding Asp.net Mvc Unobtrusive Validation” series

  1. How the jQuery validate plugin works internally
  2. Understand the Html generated by the unobtrusive validation in Asp.net MVC
  3. How the unobtrusive jQuery validate plugin works internally in Asp.net MVC

What we will be talking about in this article

  • The parse method
  • parseElement section
  • skipAttach parameter explanation
  • parseElement function explanation
  • validateInfo section]
  • validateInfo function explanation
  • The return object explanation
  • Adapters

parse() method

we will explain the cycle of whats happening in the unobtrusive validation at the document load and will understand what is the role of every component

if we look at the end of the jquery.validate.unobtrusive.js we will find

$(function () {
  $jQval.unobtrusive.parse(document)
})

so we called the parse() method and pass it document

so whats the parse method exactly

{
  parse: function (selector) {
    ///
    <summary>
    /// Parses all the HTML elements in the specified selector. It looks for input elements decorated
    /// with the [data-val=true] attribute value and enables validation according to the data-val-*
    /// attribute values.
    /// </summary>
    ///Any valid jQuery selector.

    $(selector).find(":input[data-val=true]").each(function () {
      $jQval.unobtrusive.parseElement(this, true);
    });

    var $forms = $(selector)
      .parents("form")
      .andSelf()
      .add($(selector).find("form"))
      .filter("form");

    $forms.each(function () {
      var info = validationInfo(this);
      if (info) {
        info.attachValidation();
      }
    });
  }
}

There is two sections in the parse method

1- parseElement() section

parseElement(element, skipAttach)

$(selector)
  .find(':input[data-val=true]')
  .each(function () {
    $jQval.unobtrusive.parseElement(this, true)
  })

so the first thing that happens we iterate over all the elements that have a data-val=true inside the selector that we passed (its document in our case)

then call parseElement() and pass it the element we want to validate and true for skipAttach

skipAttach() parameter explanation

a question could come up why we passed true to skipAttach and not false

skipAttach is a flag for calling validate() on the form

If we passed false it will translate the rules on the element that we have passed then immediately call validate on the rules array and pass along other options used by the unobtrusive validation. (if there is still other element to be parsed they wont)

We don’t want that. We first want to translate all the rules on every element in the form then after all the rules are translated we will call validate() which is basically the second part of the parse method

So what other scenarios we would pass true to skipAttach ?

If we want to add dynamic element to an already validated form we will pass true to skip validating the form again because it won’t do anything (we will talk about dynamic validating element in the next article)

parseElement() function

The parseElement() does mainly two things

  1. On the first call on an element in a form (No element was called before it in the same form)

it will construct an object of options that will be passed to the validate() method, the options are used by the jquery.unobtrusive like custom errorPlacement function, custom errorClass and an empty rules array

Note: what is responsible of doing all that is a private method called validationInfo(form) that is called within parseElement and when its called more than 1 time it will just return the same object so we can add, modify or call data/functions in it

  1. For every element when we call parseElement it will understand the rules that are written on this element (the data-*) using the adapters (Will explain later how the adapters works) and then translate and add them to the rules array that was constructed in the first call

every call to parseElement its result will be saved on the form itself using $.data(“unobtrusiveValidation“) that’s how the separate calls sync in the same data source

2- validateInfo() section

var $forms = $(selector)
  .parents('form')
  .andSelf()
  .add($(selector).find('form'))
  .filter('form')

$forms.each(function () {
  var info = validationInfo(this)
  if (info) {
    info.attachValidation()
  }
})

validateInfo() function explanation

we already said calling validateInfo() will construct an object of options for the validate() method, the options are used by the jquery.unobtrusive like a custom errorPlacement function, custom errorClass and an empty rules array

here we also called validationInfo() for every form in the page basically at this point calling validationInfo() we will only get the object stored on the form that was already populated in the first phase so we are using it as a getter

after that we are calling attachValidation() which is basically calling the validate() method passing it all the options populated by the validationInfo()

data_validation = 'unobtrusiveValidation'

function validationInfo(form) {
  var $form = $(form),
    result = $form.data(data_validation),
    onResetProxy = $.proxy(onReset, form)

  if (!result) {
    result = {
      options: {
        // options structure passed to jQuery Validate’s validate() method
        errorClass: 'input-validation-error',
        errorElement: 'span',
        errorPlacement: $.proxy(onError, form),
        invalidHandler: $.proxy(onErrors, form),
        messages: {},
        rules: {},
        success: $.proxy(onSuccess, form),
      },
      attachValidation: function () {
        $form
          .unbind('reset.' + data_validation, onResetProxy)
          .bind('reset.' + data_validation, onResetProxy)
          .validate(this.options)
      },
      validate: function () {
        // a validation function that is called by unobtrusive Ajax
        $form.validate()
        return $form.valid()
      },
    }
    $form.data(data_validation, result)
  }
  return result
}

first we are checking if we already called this function on the form before by using $form.data(“unobtrusiveValidation“) if we did then do nothing and return the result

The return object explanation

If its the first time we call validationInfo() then we construct a result object and will save it on the form using $.data() method this object will contain 3 parts:

  • An object which is the basically all the options that we will pass to the validate() method with an empty rules & messages arrays that will be constructed later
  • attachValidation() method will bind a custom event to the form itself “reset.unobtrusiveValidation” and after call validate on the method with all the options , this method will be called when the rules & messages arrays are completed  (triggering the custom "reset" event will call onReset() method which will basically resets everything)
function onReset(event) {
  // ‘this’ is the form element
  var $form = $(this)
  $form.data('validator').resetForm()
  $form.find('.control-group').removeClass('error')
  $form
    .find('.validation-summary-errors')
    .addClass('validation-summary-valid')
    .removeClass('validation-summary-errors')
  $form
    .find('.field-validation-error')
    .addClass('field-validation-valid')
    .removeClass('field-validation-error')
    .removeData('unobtrusiveContainer')
    .find('>*') // If we were using valmsg-replace, get the underlying error
    .removeData('unobtrusiveContainer')
}

so if we want to trigger the reset event to reset the form

$(‘form’).trigger(‘reset.unobtrusiveValidation’)
  • a custom validate() method that will be called from unobtrusive ajax

Adapters

I intentionally left the adapters section out when i talked about the parseElement() method because its complicated enough to be in a sub-section

We looked at how Html is generated using unobtrusive validation and how to add custom validation attribute in normal jquery.validate what links the two is the Adapters

So What is the adapter’s responsibility ?

it is responsible for translating the Html data-* to a format that can be understood by the normal jquery.validate

If a user want to add a custom validation method using the unobtrusive validation he must also provide an adapter for it

the adapters collection resides in *$.validator.unobtrusive.adapters

  • the adapters collection consist of all the default adapters defined by default in jquery.unobstrusive and the ones that the user has defined
  • It also contains 4 methods for adding custom adapters that we will take a look at later

so lets look at the most generic method which is

jQuery.validator.unobtrusive.adapters.add(adapterName, [params], fn)

you can consider this method the $.ajax method and the other three are helper methods that uses it

so lets explain the parameters

  • adapterName: is the adapter name as the name implies , and it matches the ruleName in the Html element data-val-ruleName

  • [params]: an optional parameter array that the validation method would use to complete validation

  • fn: Is called to map the Html data-* to rules and messages used by the validate() method and it has a parameter option passed to it which is an object containing the following properties:

  • element: the Html element being validated

  • form: the form element

  • message: the error message for this rule extracted from data-* attribute on the element

  • params: parameters that are used for the validation and its an array extracted from the data-* attributes on the Html element data-val-ruleName-param1

  • rules: The jquery rules array for this element , your expected to add to this array the rule(s) that this adapter is used for you will pass key/value pairs

  • the key is the validation rule name ,

  • the value is the parameters used for this rule (check this section in that article for adding custom rules to jquery.validate)

  • messages: The jquery messages array for this element, same as the rules object you are expected to fill it and its used as the messages object for this Html element in the validate method

There is no return result from the method. Whats happening manipulating the rules & messages arrays will directly be saved on the form itself using $.data(“unobtrusiveValidation") you can check the parseElement method for the details of how the parameter where passed to the adapter function

Example:

<input
  id="val"
  type="text"
  name="val"
  data-val="true"
  data-val-between="Must be in the right range"
  data-val-between-min="5"
  data-val-between-max="30"
/>
//The adapter
jQuery.validator.unobtrusive.adapters.add(
  ‘between’, [‘min’ ,’max’],
  function (options) {
    options.rules[‘between’] = {
      min: options.params.min,
      max: options.params.max
    };
    options.messages[‘between’] = options.message;
  }
);

//The validation method
jQuery.validator.addMethod("between", function (value, element, params) {
  params.min == 5; //true
  params.max == 30; //true
});

so what about the other adapters

  • addBool
  • addSingleVal
  • addMinMax

They are all pretty simple if you understood the concept of adding a custom adapter using the add() method explained before

You can check Brad Wilson’s article he explained in it the adapters in depth


Nadeem Khedr

Written by Nadeem Khedr citizen of the world, you can find me on Twitter & Github