A jQuery Development Pattern

After dealing with jQuery for about 7 months, I've finally settled into my own style of plugin development. This article will discuss what I feel are the shortcomings of the current jQuery development pattern, and my alternative: the class based plugin development pattern.

Shortcomings of the Current Plugin Pattern

The Plugin Pattern Clutters the Arguments

It's not uncommon to see a plugin that takes multiple objects and multiple anonymous functions in as arguments. The result is extremely cluttered, borderline unreadable syntactical soup. For instance, take this example, provided in the documentation of the most popular jQuery validation plugin:
  $("form").validate({
    invalidHandler: function(e, validator) {
      var errors = validator.numberOfInvalids();
      if (errors) {
        var message = errors == 1
          ? 'You missed 1 field. It has been highlighted below'
          : 'You missed ' + errors + ' fields.';
        $("div.error span").html(message);
        $("div.error").show();
      } else {
        $("div.error").hide();
      }
    },
    onkeyup: false,
    submitHandler: function() {
      $("div.error").hide();
      alert("submit! use link below to go to the other step");
    },
    messages: {
      password2: {
        required: " ",
        equalTo: "Please enter the same password as above"  
      },
      email: {
        required: " ",
        email: "Please enter a valid email address, example: you@yourdomain.com",
        remote: jQuery.validator.format("{0} is already taken, please enter a different address.")  
      }
    },
    debug:true
  });
As the code user's needs grow, so does the complexity of the plugin instantiation. It's easy to get lost in the sea of curly braces, especially once conditional statements and objects within objects are thrown into the equation. From lines 19-23, you can see that the plugin actually goes 3 layers deep in objects.

Solution

Keep the plugin instantiation for the most basic settings. Use public methods to take care of the more complex customizations. The easiest way to accomplish this adjustment is to use JavaScripts "new" operator in conjunction with some object-oriented principles. If the plugin had been authored using a class(prototype) model, the equivalent configuration would look like so:
  var form = new validate("form", 
  {
    onkeyup: false,
    debug: true
  });
  
  form.invalidHandler(function(e, validator) 
  {
    var errors = form.numberOfInvalids();
    if (errors) 
    {
      var message = errors == 1
        ? 'You missed 1 field. It has been highlighted below'
        : 'You missed ' + errors + ' fields.';
      $("div.error span").html(message);
      $("div.error").show();
    } 
    else 
    {
      $("div.error").hide();
    }
  });
  
  form.submitHandler(function() 
  {
    $("div.error").hide();
    alert("submit! use link below to go to the other step");
  });
    
  form.message("password2", 
  {
    required: " ",
    equalTo: "Please enter the same password as above"
  });
  
  form.message("email",
  {
    required: " ",
    email: "Please enter a valid email address, example: you@yourdomain.com",
    remote: jQuery.validator.format("{0} is already taken, please enter a different address.")  
  });
The separation of handlers and basic configuration promotes readability and organization. Additionally, the start and end of configuration options are now explicitly clear. Adjusting or removing configurations would be extremely easy. These conveniences also extend to the plugin source itself.

Many Complex Plugins Simply Do Too Much

As a plugin becomes increasingly complex, it becomes increasingly difficult to preserve the reason why plugins are developed in the first place: ease of use in projects. Remember that the plugin should conform to the wishes of the developer, not the other way around. Many plugin developers strive to make their plugin work with an empty argument call, such as $("selector").myplugin();. There's an underlying notion amongst developers that an empty argument call equates to ultimate ease of use, and this is rarely the case. A complex plugin with an empty argument call usually means the code user has to alter large quantities of HTML and/or CSS. At that point, the zero argument call is nothing but meaningless flare. Notice my emphasis on "complex plugins". A simple plugin, such as a plugin that fades in elements in sequence, could benefit greatly from an empty argument call. My main point is Do not devoid the plugin user of all responsibility. If you adequately document your plugin, it is up to the plugin user to implement it correctly. There are going to be some developers that will fail to use your code successfully. That's okay. What's not okay is absolving the plugin user of responsibility to the extent that they can no longer customize the plugin implementation to fit their specific needs. No one knows the full capabilities of their code. I'm sure John Resig, creator of jQuery, has been surprised at what's been created by the development community with his code. You might think the plugin you're authoring is only good for a certain situation, but another developer with a different mindset might find it useful in another situation. The moment you hard-code your situation into the plugin, you eliminate other, possibly better, ideas. The ultimate goal of your plugin should be to make a developer's idea easy to execute. That idea might be brilliant. That idea might be terrible. Either way, your job is to make that idea simple to put in motion.

Class Based Plugin Development Pattern

Now that we've discussed the shortcomings of the current plugin development pattern, lets discuss an alternative: the class-based plugin development pattern. This pattern requires you to know the object oriented basics of JavaScript. Whether you decide to implement this pattern or not, I highly encourage you to learn the object oriented fundamentals found in that article.

Defining the Plugin

Defining the plugin with the class-based pattern is similar to the normal plugin pattern. Normal plugin pattern:
jQuery.fn.myPlugin = function()
{
     //Code stuff goes here!
};
Class-based plugin pattern
function myPlugin()
{
     //Code stuff goes here!
}

Function Header and Default Values

Since you're creating a plugin, chances are you're wanting to manipulate an element that can be represented by a jQuery selector. Thus, the most natural function header is
function myPlugin(obj, options)
{
     //Code stuff goes here!
}
Where obj represents the jQuery selector or jQuery object for the element(s) you wish to manipulate, and options is an object with properties that will be used in the plugin. This brings up a question: Why not have obj just be a property of options? In many instances, you want default values to be values associated with the CSS properties of the elements in question. However, object members can't refer to other object members in the definition of the object. This is simply because at the time the object is being populated with values, it does not yet exist to refer to itself.
    var test1 = {val1: 10, val2: 5+test1.val1};
    alert(test1.val2); //Error, val1 undefined
    
    var test2 = {val1: 10, val2: 5+val1};
    alert(test2.val2); //Error, val1 undefined

    var test3 = {val1: 10, val2: 5+this.val1};
    alert(test3.val2); //Error, val2 Not a number
For default values, jQuery extend works perfectly. Lets say our plugin options takes in 3 properties: x, y, and z. jQuery extend allows us to fill in our options with default values in the event they aren't specified in the function call.
function myPlugin(obj, options)
{
    //------------------------------------------------------------
    // Set default property values
    //------------------------------------------------------------
    var defaults = {
    x:0,
    y: 0,
    z: 0
    },  settings = jQuery.extend(defaults,options);
}
The settings object is the options object populated with the values of default in the instance a value from options is missing. For example, if we passed in {x: 5, y:10} as our options object, settings.x would be 5, settings.y would be 10, and settings.z would be 0. Since z was not specified in the options object, the value of z associated with the defaults object was placed in the settings object. Throughout the plugin, you should refer to the option values via the settings object.

Constructor Methods

If you have any events that you would like to take place once the plugin is initiated, you should have those executed immediately after declaring your default values. For example, if we wanted to make our jQuery selected object have an absolute position at coordinates (x,y),
function myPlugin(obj, options)
{
    //------------------------------------------------------------
    // Set default property values
    //------------------------------------------------------------
    var defaults = {
    x:0,
    y: 0,
    z: 0
    },  settings = jQuery.extend(defaults,options);

    //Do this immediately 
    $(obj).css({'position': 'absolute', 
                    'left': settings.x, 
                    'top': settings.y});
}

Private Methods and Properties

Private methods are functions that can only be called within the plugin definition. Similarly, private properties are properties that can only be accessed within the plugin definition. Private methods are useful for calculations and callbacks. Private methods promote readability for those viewing/maintaining the code. Since JavaScript exhibits function scope, private methods and properties can be created by simply using the "var" statement.
function myPlugin(obj, options)
{
    //------------------------------------------------------------
    // Set default property values
    //------------------------------------------------------------
    var defaults = {
    x:0,
    y: 0,
    z: 0
    },  settings = jQuery.extend(defaults,options);

    //Do this immediately 
    $(obj).css({'position': 'absolute', 
                    'left': settings.x, 
                    'top': settings.y});
    
     //Cannot be accessed by calling environment
     var callback = function()
     {
          alert("Callbacked!");
     };
}

Public Methods

Public methods are functions that can be accessed by the calling environment. Notice that public properties are not listed, as most of the time public properties are poor programming practice. You don't want the values inside the plugin to be altered by the outside without validation. Public methods are extremely useful for initiating actions, setting values, validation of arguments, and my personal favorite, altering callbacks. To make a method public, you attribute it to the plugin itself with the "this" keyword. Lets say we want our plugin to allow the user to move the elements in the selected jQuery object to a certain coordinate. After the element has moved, we want a custom callback to occur.
function myPlugin(obj, options)
{
    //------------------------------------------------------------
    // Set default property values
    //------------------------------------------------------------
    var defaults = {
    x:0,
    y: 0,
    z: 0
    },  settings = jQuery.extend(defaults,options);

    //Do this immediately 
    $(obj).css({'position': 'absolute', 
                    'left': settings.x, 
                    'top': settings.y});
    
     //Cannot be accessed by calling environment
     var callback = function()
     {
          alert("Callbacked!");
     };

     //Replace the callback with a custom callback
     this.callback = function(func)
     {
          callback = func;
     };

     //Animate the element to a coordinate, then do the callback
     this.move = function(x,y)
     {
          //Make sure the values are positive.
          x = Math.abs(x);
          y = Math.abs(y);

          $(obj).stop(true,true).animate({'left': x, 'top': y}, 500, 
          function() {
          callback(); 
          });          
     };
}

Calling the Plugin

Now that we have our Plugin defined, it's time to call it. Plugins under this pattern will be called using the "new" operator, and it must be assigned to a variable. While this might feel awkward at first, you'll grow comfortable with this instantiation over time.
var item = new myPlugin("#box", 
{
     x: 20,
     y: 30
});
Once this statement is executed, the constructor of the plugin occurs. Thus, the element with a div ID of "box" will be positioned absolutely with left value at 20 and top value at 30. In order to access public method, use the "." operator along with the name of the variable used.
$(document).ready(function()
{
     var item = new myPlugin("#box",
     {
          x: 20,
          y: 30
     });

    var x = 0;
    var y = 0;
     
    //Alters the default callback function
    item.callback(function()
    {
        $("#box").html("Left: " + x + "<br />Top: " + y);
    });

    //Moves box when clicked
    $("#box").click(function()
    {
       item.move(x, y);
       x += 10;
       y += 10;
    });
});

Demo of our little plugin

So we've spent some time creating this little plugin, and you might be attached after all this work. I understand! Feel free to view this plugin in action and download the source code. Note how our plugin by itself is not hard-coded to move the element every time it's clicked and display the coordinates in the element. All that was done outside of the plugin. It's that type of versatility that makes me really enjoy this development pattern.

A Real Example

To see this development pattern in action, check out my jQuery sticky scroller plugin.

Feedback

If you have any feedback or suggestions, please leave them in the comments below! January 01, 2011
About the Author:

Joseph is the lead developer of Vert Studios Follow Joseph on Twitter: @Joe_Query
Subscribe to the blog: RSS
Visit Joseph's site: joequery.me