Book Image

Instant jQuery Boilerplate for Plugins

By : Jonathan Fielding
Book Image

Instant jQuery Boilerplate for Plugins

By: Jonathan Fielding

Overview of this book

jQuery plugins helps you to extend jQuery's prototype object. jQuery plugins are used to achieve a specific task with a collection of elements, they are modular and reusable across several projects.Instant jQuery Boilerplate for Plugins is a hands on guide to writing your own plugins. The recipes in this book will guide you through the steps of plug-in development.Each recipe is a short tutorial within itself that will help you explore different options available within jQuery by providing a clear explanation on what jQuery Boilerplate has to offer. By the end of the book, you will have learned how to write a plugin and will be ready to start writing plugins of your own.
Table of Contents (7 chapters)

Writing a validation plugin using jQuery Boilerplate (Intermediate)


Basic form validation can be extremely simple to implement with a plugin, but writing a plugin that serves a large number of use cases can be difficult. As such, this section shall show how to build a robust form validation plugin, which validates required e-mail and phone number input fields.

Getting ready

We firstly need our plugin development template created earlier in the book.

How to do it...

  1. Our first step is to work on our HTML; for this plugin, we want the JavaScript to require minimal setup once it is pointed to a form, which it needs to validate. To do this, we will need to add data attributes to HTML that define what validation needs to be performed and the error messages that will need to be displayed to the users.

  2. The first data attribute we need to add to each of the input elements is data-validate. This data attribute is used to list all the different validators we want to run against the control. In the following example of an email field, we will set data-validate to required email, which tells JavaScript that we need to validate and check whether an e-mail address has been entered in the field:

    <input type="text" name="email" data-validate="required email"/>
  3. We also want to allow the user of our plugin to customize their error messages; to do this, we will take a similar approach to how we define the validators the element uses. Continuing with the example of an email field, we will need to provide error messages for both when the e-mail address has not been filled in and when the e-mail address provided is invalid. To do this, we will add two data attributes: data-required, which displays an error message if the e-mail ID is not entered, and data-email, which displays an error message if the e-mail ID is not valid:

    <input type="text" name="email" data-validate="required email" data-required="Please tell us your email address" data-email="Please enter a valid email address" />
  4. Continuing on, we can now do the same for both a name field and a phone number field. The code snippet for this purpose is as follows:

    <input type="text" name="name" data-validate="required" data-required="Please tell us your name" />
    
    <input type="text" name="email" data-validate="required email" data-required="Please tell us your email address" data-email="Please enter a valid email address" />
    
    <input type="text" name="phone" data-validate="required phone" data-required="Please tell us your phone number" data-phone="Please enter a valid phone number" />
  5. Now that we have the HTML markup for our fields, we can create the rest of the form; this is simply the form element wrapping the fields and the addition of a submit button. This should be placed in our container in our index.html file, as shown in the following code snippet:

    <form action="#" method="post">
      <label for="name">Name</label>
      <input type="text" name="name" data-validate="required" data-required="Please tell us your name" />
    
      <label for="email">Email</label>
      <input type="text" name="email" data-validate="required email" data-required="Please tell us your email address" data-email="Please enter a valid email address" />
    
      <label for="phone">Phone number</label>
      <input type="text" name="phone" data-validate="required phone" data-required="Please tell us your phone number" data-phone="Please enter a valid phone number" />
    
      <input type="submit" value="Submit" />
    </form>
  6. Now that we have our HTML, we can move on to writing our plugin. The first step is to set up our plugin name. For simplicity, we will call it validation.

    var pluginName = "validation"
  7. The next step is to define our defaults. As we are already defining a lot of our settings inside the HTML, there are not many settings we need to actually configure using JavaScript. The one setting that would be useful for configuring is what HTML should be displayed to show an error message. For this, we will allow the developer using the plugin to provide a template; a default template will be stored in the default settings. To allow the error message to be displayed in the template, we will insert %E% where the error message should be outputted:

    var pluginName = "validation",
      defaults = {
        errorTemplate: '<p class="error">%E%</p>'
      };
    
  8. Before we move on to the init section of our plugin, we will spend some time writing some basic validators. These will be private and inaccessible outside of the plugin. To start with, we will set up a new object literal called validators that we will place after the Plugin.prototype object.

    var validators = {
    }
  9. We will add a series of basic validation methods to this object, for those criteria that are required such as email and phone. Each validation method will accept an object literal as a parameter, as seen in the following code snippet:

    var validators = {
      required: function(validationAttr){
    
      },
      email: function(validationAttr){
    
      },
      phone: function(validationAttr){
    
      }
    };
  10. Starting with the required method, we need to check that its value is not null, that it is not an empty string, and that the value does not equal the default value. This is accomplished by the following code:

    required: function(validationAttr){
      var valid = true;
    
      if (validationAttr.value === null || validationAttr.value === '') {
        valid = false;
      }
    
      return valid;
    },
  11. Moving on to the e-mail validation, we need to use a regular expression to validate that the value added is in fact an e-mail address. To test the regular expression, we will use .match, as shown:

    email: function(validationAttr){
      var valid = false;
    
      if (validationAttr.value.match(/^((?:(?:(?:\w[\.\-\+]?)*)\w)+)\@((?:(?:(?:\w[\.\-\+]?){0,62})\w)+)\.(\w{2,6})$/)) {
        valid = true;
      }
    
      return valid;
    },
  12. The final validation method is for phone validation. We will use a regular expression to validate the phone number. To test the regular expression, we will use .match, as shown:

    phone: function(validationAttr){
      var valid = false;
    
      if (validationAttr.value.match(/^(0(\d|\s){8,12}$)|(\+(\d|\s){8,12}$)/)) {
        valid = true;
      }
    
      return valid;
    }
  13. We can now move on to the init section of our plugin. To validate the form, we need to handle two types of events, the submission of the form and the changing of the value of the form element. The prerequisite for our plugin to work is that it should be applied to the form element; we can then use event delegation to add the two types of events directly to the form. We will pass three parameters to the jQuery.on() method, firstly the event, secondly the selector, and finally the event handler method that we will shortly be adding to our prototype object. For the change event, we will use [data-validate] as the selector, which will select any form element that we have added validation to. For the form submission, we will add a submit event to the form., as shown:

    init: function() {
      $(this.element).on('change','[data-validate]',this.validateField);
      $(this.element).on('submit',this.validateForm);
    },
  14. The next step is to set up our methods, the first being validateForm and the second, validateField. The validateForm method will accept the jQuery event object as its only parameter and the validateField method will take the jQuery event object and a parameter called that. The code snippet for this is as follows:

    Plugin.prototype = {
      init: function() {
        $(this.element).on('change','[data-validate]',this.validateField);
        $(this.element).on('click',this.validateForm);
      },
      validateForm: function(e){
    
      },
      validateField: function(e,that){
    
      }
    };
  15. The first method we will get started with is the validateField method that will validate any field. When we added the validateForm method to Plugin.prototype, you may have noticed that we pass two parameters to the method. The first parameter is the jQuery object that is passed to the method by jQuery. The second parameter is an optional parameter called that (a pseudonym for this used where validateField is called in a way that makes this have an incorrect scope).

  16. At the start of the validateField method, we will setup several different variables:

    • valid: The valid variable keeps track of whether the field is valid or not.

    • field: The field variable defaults to equaling that. If that were not passed to the method as a parameter, the field becomes equal to this.

    • $field: Once we have decided whether the field attribute is equal to that or this, we will get the jQuery object of the field attribute and store it as $field.

    • requiredValidators: The requiredValidators variable stores an array of validators required to be executed on the form field. This is retrieved from the data-validate data attribute using .attr('data-validate') and then splitting it into an array using .split(' ').

    • plugin: The plugin variable stores the current instance of the plugin retrieved using $(e.delegateTarget).data("plugin_" + pluginName).

    • $error: Any existing error message for a field will have their jQuery object cached using jQuery.data(). These can then be retrieved and removed.

    • errorHTML: The errorHTML will be used for building up the error message's HTML page.

      validateField: function(e,that){
        var valid = true,
        field = that || this,
        $field = $(field),
        requiredValidators = $field.attr('data-validate').split(' '),
        plugin = $(e.delegateTarget).data("plugin_" + pluginName),
        $error = $field.data('error') || null,
        errorHTML = null;
      }
  17. After setting up our variables, we can now remove any existing error messages, as shown in the following code snippet:

    validateField: function(e,that){
      var valid = true,
      field = that || this,
      $field = $(field),
      requiredValidators = $field.attr('data-validate').split(' '),
      plugin = $(e.delegateTarget).data("plugin_" + pluginName),
      $error = $field.data('error') || null,
      errorHTML = null;
    
      if($error !== null){
        $error.remove();
      }
    }
  18. Now that we have removed any existing error messages, we can start with validating the field. The first step in validating the field is to set up the validationAttr object literal with the value of the element. The code to achieve this is as follows:

    validateField: function(e,that){
      var valid = true,
        field = that || this,
        $field = $(field),
        requiredValidators = $field.attr('data-validate').split(' '),
        plugin = $(e.delegateTarget).data("plugin_" + pluginName),
        $error = $field.data('error') || null,
        errorHTML = null;
    
      if($error !== null){
        $error.remove();
      }
    
      var validationAttr = {
        value: $field.val()
      };
    }
  19. We now need to go through each of the items in the requiredValidators array and run each of the validators. We will use a for loop to go through the array of requiredValidators as shown:

    validateField: function(e,that){
      var valid = true,
        field = that || this,
        $field = $(field),
        requiredValidators = $field.attr('data-validate').split(' '),
        plugin = $(e.delegateTarget).data("plugin_" + pluginName),
        $error = $field.data('error') || null,
        errorHTML = null;
    
      if($error !== null){
        $error.remove();
      }
    
      var validationAttr = {
        value: $field.val()
      };
    
      for (var i = 0; i < requiredValidators.length; i++) {
      }
    }
  20. Inside the for loop, we now need to run the correct validation. We can do this by running validators[requiredValidators[i]](validationAttr). Breaking down this statement, we find that the validators[requiredValidators[i]] part of the code selects the method and the (validationAttr) part executes the method with the validation attributes object as the parameter. The validation method is executed as a part of an if statement. If the validation fails, it will execute the code required to set the failed state. This can be understood better by the following code snippet:

    validateField: function(e,that){
      var valid = true,
        field = that || this,
        $field = $(field),
        requiredValidators = $field.attr('data-validate').split(' '),
        plugin = $(e.delegateTarget).data("plugin_" + pluginName),
        $error = $field.data('error') || null,
        errorHTML = null;
    
      if($error !== null){
        $error.remove();
      }
    
      var validationAttr = {
        value: $field.val()
      };
    
      for (var i = 0; i < requiredValidators.length; i++) {
    
        if(validators[requiredValidators[i]](validationAttr) === false){
          
        }
      }
    }
  21. We now need to handle what happens if the validation fails. Inside our if statement, we need to add the functionality to show an error message. The error message needs to use the error message template that we defined in the template combined with the text for the error message, which is defined on the field as a data attribute. To do this, we need to set the errorHTML variable to the value returned by using JavaScript's .replace() on plugin.options.errorTemplate to replace %E% with the value of the current validators, error message.

  22. We then need to append the error message to the page; simultaneously, we also want to cache the error element jQuery object. We can do this all in one line of code by using the jQuery.data method to store an error on the field. As the value of the data we are adding, we can use $(errorHTML).insertAfter($field) that returns the error message of the corresponding jQuery object, which is now cached using .data().

  23. Finally, before we close our if statement, we can set valid equal to false and break out of our loop using break. The code snippet for this purpose is as follows:

    validateField: function(e,that){
      var valid = true,
        field = that || this,
        $field = $(field),
        requiredValidators = $field.attr('data-validate').split(' '),
        plugin = $(e.delegateTarget).data("plugin_" + pluginName),
        $error = $field.data('error') || null,
        errorHTML = null;
    
      if($error !== null){
        $error.remove();
      }
    
      var validationAttr = {
        value: $field.val()
      };
    
      for (var i = 0; i < requiredValidators.length; i++) {
    
        if(validators[requiredValidators[i]](validationAttr) === false){
          errorHTML = plugin.options.errorTemplate.replace("%E%", $field.attr('data-'+requiredValidators[i]));
          $field.data('error',$(errorHTML).insertAfter($field));
    
          valid = false;
          break;
        }
      }
    
      return valid;
    }
  24. At this point, our elements on change functionality should be working; however, the next step is to add the functionality for when the form is submitted.

  25. We can now get started with adding the functionality to the validateForm method. The first step is to define our variables. For this method, we will need a simple Boolean variable named valid, and a plugin variable that we will load the plugin instance into.

    validateForm: function(e){
      var valid = true, 
        plugin = $(this).data("plugin_" + pluginName);
    },
  26. Our next step is to go through the form looking for elements we want to validate. We will do this by finding elements with the data-validate attribute set and then use jQuery.each() to loop through each of these, as shown in the following code snippet:

    validateForm: function(e){
      var valid = true, 
       plugin = $(this).data("plugin_" + pluginName);
      $(plugin.element).find('[data-validate]').each(function(){
      });
    },
  27. Now that we are looping through our fields, we can add some validation rather than duplicate the change validation, which we have already written. We will simply use an if statement to check the result of the validateField method on each element. If at any point the validateField method returns the value as false, we set valid equal to false as the form has failed validation.

    validateForm: function(e){
      var valid = true, plugin = $(this).data("plugin_" + pluginName);
    
      $(plugin.element).find('[data-validate]').each(function(){
        if(plugin.validateField(e,this) === false){
          valid = false;
        }
      });
    },
  28. Our final step is to return the valid variable.

    validateForm: function(e){
      var valid = true, plugin = $(this).data("plugin_" + pluginName);
    
      $(plugin.element).find('[data-validate]').each(function(){
        if(plugin.validateField(e,this) === false){
          valid = false;
        }
      });
    
      return valid;
    },

There's more...

It is important that our validators work as we expect and that, if we go on to improve our plugin by adding extra validators, we should perform a regression test on our code. While we could manually test each of our validation methods, it makes sense to write some unit tests to test the code.

Unfortunately our plugin isn't quite ready for unit testing as our validators are nested within a closure and we are therefore unable to call them directly.

  1. The first step is for us to add a new method to our plugin prototype, which will act as a pass-through for our tests. This method needs to be minimal as we don't want to be testing the pass through; we want to test the actual validator. We will call our new method validateString, and it will simply take the options passed to the plugin and perform the validation. As the validators expect to receive an object literal, we will setup the object literal with the value. We then simply return the value returned by the validator, as shown in the following code snippet:

    validateString: function(options){
      var validationAttr = {
        value: options.val
      };
      return validators[options.validator](validationAttr);
    }
  2. With our new method complete, we now need to update the plugin wrapper to add support for calling the validateString method. We will need to add an if statement to determine whether the plugin should run as normal or whether it is being used to simply validate a string.

    $.fn[pluginName] = function ( options, methodOptions) {
      var localPlugin = null;
    
      if (options === undefined || typeof options === 'object') {
        return this.each(function () {
          if (!$.data(this, "plugin_" + pluginName)) {
            $.data(this, "plugin_" + pluginName, new Plugin( this, options ));
          }
        });
      }
      else if(options === "validateString"){
        localPlugin = new Plugin( null, {} );
        return localPlugin.validateString(methodOptions);
      }
    };
  3. Now that we have updated our plugin, we can test this out in the browser console. Once you have the page open and have opened the console, you can test the new version of the plugin with:

    $().validation('validateString',{val: "test", validator: "required"});
  4. This should simply return true to the console.

  5. Now that we are happy that our changes to our plugin are working, we will start setting up QUnit. The first step is to create a new HTML file called test.html, which will be used for our unit tests. We will use the QUnit CSS and JavaScript both directly from the jQuery site, so the only extra file we need to create is the test.js file that will house our tests.

    <!DOCTYPE html>
    <html>
      <head>
        <title></title>
        <link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.11.0.css">
      </head>
      <body>
        <div id="qunit"></div>
        <div id="qunit-fixture"></div>
    
        <script src="js/vendor/jquery-1.9.1.min.js"></script>
        <script src="js/plugin.js"></script>
        <script src="http://code.jquery.com/qunit/qunit-1.11.0.js"></script>
        <script src="js/tests.js"></script>
      </body>
    </html>
  6. The next step is our tests.js file. There are three main test cases we want to create, the first to test the required validation, the second to test the e-mail validation, and the third to test the phone number validation.

  7. Starting with the required validation test case, our first step is to create our test case. This is done by executing the test method with two properties. The first is the name of the test case, and the second is a callback method where we will add all the individual assertions:

    test( "Required validation", function() {
    });
  8. When adding our assertions, we are going to use the ok method to which we will pass two parameters. The first is our condition and the second is the name of the individual assertion. For the condition parameter, we will simply check that the plugin validation method is returning what we expect; in our case, the values will equal true or false. For our first assertion, we will set the value to "", and we expect that this will return false as it is an empty string; for our second assertion, we will pass Required value, and we expect that this will return true as it is a populated value:

    test( "Required validation", function() {
      ok($().validation('validateString',{val: "", validator: "required"}) === false, "Value is not entered");
      ok($().validation('validateString',{val: "Required value", validator: "required"}) === true, "Value is entered");
    });
  9. The next step is to do the same for the e-mail validation: for our first assertion we will set the value to hello and we expect that this will return false as it is not a valid e-mail address; for our second assertion, we will pass [email protected], and we expect that this will return true as it is a valid e-mail address.

    test( "Email Validation", function() {
      ok($().validation('validateString',{val: "hello", validator: "email"}) === false, "Value is not email address");
      ok($().validation('validateString',{val: "[email protected]", validator: "email"}) === true, "Value is email address");
    });
  10. Finally we will add our test case for the phone number validation, for our first assertion we will set the value to "hello" and we expect that this will return false as it is not a valid phone number, for our second assertion we will pass "01234789777" and we expect that this will return true as it is a phone number.

    test( "Phone Number Validation", function() {
      ok($().validation('validateString',{val: "hello", validator: "phone"}) === false, "Value is not phone number");
      ok($().validation('validateString',{val: "01234789777", validator: "phone"}) === true, "Value is a phone number");
    });
  11. Upon opening this in the browser, we will be presented with the tests; if you look at each test case you will notice (0,2,2). These values indicate there were zero failures, two passes out of a total of two assertions.

To help you with your own plugin development, you should take advantage of the many resources available on the Web.

jQuery plugins documentation

The official documentation for information about jQuery Plugins can be found at: http://learn.jquery.com/plugins/

GitHub help

Information about how to use GitHub is available at https://help.github.com/.

JonathanFielding.com

My blog that has tutorials, jQuery plugins, and a portfolio of my work: http://www.jonathanfielding.com.