debuggable

 
Contact Us
 

How to write jQuery plugins

Posted on 29/3/12 by Tim Koschützki

jQuery, the most popular javascript library out there, is great for DOM abstraction. It allows you to encapsulate functionality into your own plugins, which is a great way to write reusable code. However, jQuery's rules for writing plugins are very loose, which leads to different plugin development practices - some of which are pretty poor.

With this article I want to provide a simple plugin development pattern that will work in many situations. If the functionality you would like to encapsulate is large and really complex, jQuery plugins are probably not what you should use in the first place. You'd rather use something like BackboneJS or jQuery.Controller in this case.

If you can't or don't want to use Backbone, you might still get away with my solution ...

Starting off

;(function($, doc, win) {
  "use strict";

  // plugin code will come here

})(jQuery, document, window);

The semi-colon before the function invocation keeps the plugin from breaking if our plugin is concatenated with other scripts that are not closed properly.

"use strict"; puts our code into strict mode, which catches some common coding problems by throwing exceptions, prevents/throws errors when relatively "unsafe" actions are taken and disables Javascript features that are confusing or poorly thought out. To read about this in detail, please check ECMAScript 5 Strict Mode, JSON, and More by John Resig.

Wrapping the jQuery object into the dollar sign via a closure avoids conflicts with other libraries that also use the dollar sign as an abbreviation. window and document are passed through as local variables rather than as globals, because this speeds up the resolution process and can be more efficiently minified.

Invoking our plugin

;(function($, doc, win) {
  "use strict";

  function Widget(el, opts) {
    this.$el  = $(el);
    this.opts = opts;

    this.init();
  }

  Widget.prototype.init = function() {

  };

  $.fn.widget = function(opts) {
    return this.each(function() {
      new Widget(this, opts);
    });
  };

})(jQuery, document, window);

$('#mywidget').widget({optionA: 'a', optionB: 'b'});

We invoke our plugin on a jQuery object or jQuery set by simply calling our widget() method on it and pass it some options. Never forget about "return this.each(function() { ... })" in order to not break the chain-ability of jQuery objects.

The main functionality of the plugin is encapsulated into a separate Widget class, which we instantiate for each member in our jQuery set. Now all functionality is encapsulated in these wrapper objects. The constructor is designed to just keep track of the passed options and the DOM element that the widget was initialized on.

You could also keep track of more sub-elements here to avoid having to always .find() them (think of performance) as you need them:

;(function($, doc, win) {
  "use strict";

  function Widget(el, opts) {
    this.$el     = $(el);
    this.opts    = opts;

    this.$header = this.$el.find('.header');
    this.$body   = this.$el.find('.body');

    this.init();
  }

  // ...
})(jQuery, document, window);

Parsing options

When we invoked the plugin we passed it some options. Often you need default options that you want to extend. This is how we bring the two together in our object's init() method:

;(function($, doc, win) {
  "use strict";

  function Widget(el, opts) {
    this.$el  = $(el);

    this.defaults = {
      optionA: 'someOption',
      optionB: 'someOtherOption'
    };

    var meta  = this.$el.data('widget-plugin-opts');
    this.opts = $.extend(this.defaults, opts, meta);

    // ...
  }

  // ...
})(jQuery, document, window);

$('#mywidget').widget({optionA: 'a', optionB: 'b'});

I like keeping the default options within the constructor of the wrapper class and not outside of it. This provides the flexibility to just take the whole wrapping class and copy it to somewhere else where you might not even have jQuery available.

If the element has options for us saved within its data attributes, we also want to take them into account. This is handy for when you have plugins that auto-initialize themselves (which we will do later) so that you have no way to pass options to them via their plugin invocation.

Here is an example:

<div class="widget js-widget" data-widget-plugin-opts="{"optionA":"someCoolOptionString"}">

Optional: Keeping a reference to our wrapper object in the element

It's a good idea to keep a reference to our plugin object on the DOM element, because that helps a lot with debugging using your browser's javascript console later. I don't always do this because it may just be overkill for the situation at hand. But I want to show you how to do this regardless:

;(function($, doc, win) {
  "use strict";

  var name = 'js-widget';

  function Widget(el, opts) {
    this.$el  = $(el);

    this.defaults = {
      optionA: 'someOption',
      optionB: 'someOtherOption'
    };

    // let's use our name variable here as well for our meta options
    var meta  = this.$el.data(name + '-opts');
    this.opts = $.extend(this.defaults, opts, meta);

    this.$el.data(name, this);

    // ...
  }

  // ...
})(jQuery, document, window);

$('#mywidget').widget({optionA: 'a', optionB: 'b'});

console.log($('#mywidget').data('js-widget'));

As you can see, we just expose our wrapper object using jQuery's $.data function. Easy.

Binding some events

Let's use our wrapper object's init() function to bind some events and write some real plugin code:

;(function($, doc, win) {
  "use strict";

  var name = 'js-widget';

  function Widget(el, opts) {
    this.$el      = $(el);

    this.defaults = {
      optionA: 'someOption',
      optionB: 'someOtherOption'
    };

    var meta     = this.$el.data(name + '-opts');
    this.opts    = $.extend(this.defaults, opts, meta);

    this.$el.data(name, this);

    this.$header = this.$el.find('.header');
    this.$body   = this.$el.find('.body');
  }

  Widget.prototype.init = function() {
    var self = this;

    this.$header.on('click.' + name, '.title', function(e) {
      e.preventDefault();

      self.editTitle();
    });

    this.$header.on('change.' + name, 'select', function(e) {
      e.preventDefault();

      self.saveTitle();
    });
  };

  Widget.prototype.editTitle = function() {
    this.$header.addClass('editing');
  };

  Widget.prototype.saveTitle = function() {
    var val = this.$header.find('.title').val();
    // save val to database

    this.$header.removeClass('editing');
  };

  // ...
})(jQuery, document, window);

Notice that we have bound the events via .on() in delegation mode, which means that our .title element doesn't even have to be in the DOM yet when the events are bound. It's generally good practice to use event delegation, as you do not have to constantly bind/unbind events as elements are added/removed to/from the DOM.

We use our name variable as an event namespace here, which allows easy unbinding later without removing event listeners on the widget elements that were not bound using our plugin.

How to prevent designers from breaking your plugins

Something else that I like doing is attaching different classes to elements depending on if they are meant for css styling or for javascript functionality.

The markup that we could use for the plugin above could be:

<div class="widget">
  <div class="header"></div>
  <div class="body"></div>
</div>

And then we do:

$(function() {
  $('.widget').widget();
});

This is all fine and dandy, but now our designer comes along, changes the classes around, because he is not aware of our new plugin (or worse, he doesn't even care). This would break our client javascript functionality. So what I often do is this instead:

<div class="widget js-widget">
  <div class="header js-header"></div>
  <div class="body js-body"></div>
</div>

Then in our plugin change how we find the body and header:

function Widget(el, opts) {
  this.$el     = $(el);

  this.defaults = {
    optionA: 'someOption',
    optionB: 'someOtherOption'
  };

  var meta     = this.$el.data(name + '-opts');
  this.opts    = $.extend(this.defaults, opts, meta);

  this.$header = this.$el.find('.js-header');
  this.$body   = this.$el.find('.js-body');
}

And change our invocation:

$(function() {
  $('.js-widget').widget();
});

I agree it's a little more markup to write and you might not need to use this. Also, since we manipulate the DOM with jQuery plugins we might depend on specific tag types anyway. So if the designer changed all div's to be li's instead, it might still break our plugin. If you are in a situation where you have regressions due to frontend engineers and designers not communicating properly, using js- prefixed classes on all important elements might be a step in the right direction.

Notice how this.$header and this.$body are also agnostic to the html tag of the element that they cover.

How to remove our plugin without removing the DOM element

For large applications it's important to allow multiple plugins to operate on the same elements. For this to work, you need to be able to add and remove plugins on the same element without affecting the other plugins.

Most jQuery plugins expect you to remove the element entirely to teardown the plugin. But what if you want to remove the plugin without removing the element? We can do this using a destroy function:

Widget.prototype.destroy = function() {
  this.$el.off('.' + name);
  this.$el.find('*').off('.' + name);

  this.$el.removeData(name);
  this.$el = null;
};

It takes our local name variable again and removes all events in that namespace. It also removes the reference to the wrapper object from the element. Now we can easily remove the plugin from the outside:

$('.js-widget').data('js-widget').destroy();

By the way, if you remove the DOM element, jQuery will take care of removing all associated data and events by itself, so there is no need to worry about that case.

How to write self-initializing plugins

If you need to deal with a lot of Ajax requests in your app and then need to bind plugins on the DOM elements that were just loaded, this tip might be pretty useful for you.

What I like doing is using a PubSub implementation to automatically invoke plugins:

$(function() {
  var $document = $(document);
  $document.trigger('domloaded', $document);

  $('.some
selector').load('/my/url', function(nodes) {
    $document.trigger('ajax_loaded', nodes);
  });
});

Now we can allow the plugin to bind itself to all elements that by class definition need to be bound to it:

;(function($, doc, win) {
  "use strict";

  var name = 'js-widget';

  // wrapper object implementation, etc.

  $(doc).on('domloaded ajaxloaded', function(nodes) {
    var $nodes = $(nodes);

    var $elements = $nodes.find('.' + name);
    $elements = $elements.add($nodes.filter('.' + name));
    $elements.widget();
  });
})(jQuery, document, window);

You can also come up with your own very custom events and even namespaces to allow your plugins to talk to each other without having to know about each other.

Advantages of this:

  1. This removes a lot of boilerplate code from our app! Re-initializing plugins after an ajax request without extra codelines? No problem!

  2. We can simply remove functionality from our application by not loading a specific plugin's javascript file.

I can see two obvious disadvantages here, though:

  1. We cannot provide options to the plugin invocation via this. We'd have to rely on options bound using the html5 data property "data-js-widget-opts" (read above). In my experience this not as often needed as one would think, though.

  2. If you have a very complex app with a lot of plugins and code flying around, this PubSub mechanism might not be the most performant way of doing things. Think of 20 plugins all doing some .find() and .filter() operation on a large piece of markup that was just loaded via ajax. Ugh. :)

Conclusion

Wrapping usable code in a jQuery plugin is not always easy, but following a few guidelines makes it a much better experience. The ultimate takeaway here is to always wrap the plugin functionality in a wrapper class, so that the elements in your jQuery set that you bind the plugin to do not interfer with each other.

If your plugin is more complex, you could even use multiple wrapper classes and let their objects talk to each other. Or even cooler, try to move some of the functionality your plugin requires out into another, smaller plugin. And let the both of them talk to each other via PubSub.

The rest is a couple nice extras that made my live easier.

Here is the full skeleton again that I use when I write new plugins (rename accordingly):

;(function($, doc, win) {
  "use strict";

  var name = 'js-widget';

  function Widget(el, opts) {
    this.$el      = $(el);
    this.$el.data(name, this);

    this.defaults = {};

    var meta      = this.$el.data(name + '-opts');
    this.opts     = $.extend(this.defaults, opts, meta);

    this.init();
  }

  Widget.prototype.init = function() {
  };

  Widget.prototype.destroy = function() {
    this.$el.off('.' + name);
    this.$el.find('*').off('.' + name);
    this.$el.removeData(name);
    this.$el = null;
  };

  $.fn.widget = function(opts) {
    return this.each(function() {
      new Widget(this, opts);
    });
  };

  $(doc).on('dom_loaded ajax_loaded', function(e, nodes) {
    var $nodes = $(nodes);
    var $elements = $nodes.find('.' + name);
    $elements = $elements.add($nodes.filter('.' + name));

    $elements.widget();
  });
})(jQuery, document, window);

Kind regards, Tim

@tim_kos

 
&nsbp;

You can skip to the end and add a comment.

Jörn Zaefferer said on Mar 29, 2012:

The pattern you describe is really close to what the jQuery UI widget factory abstracts for you. It goes a little further though, and with the updates we're landing in 1.9, its a pretty good replacement for Backbone.View.

More here: http://jqueryui.com/demos/widget/ and here http://wiki.jqueryui.com/w/page/12138135/Widget%20factory

Sebastian  said on Mar 30, 2012:

Really nice! Thank you very much, i learned a lot!

Greetings,
Sebastian

Ben  said on Mar 30, 2012:

Tim,

Thanks for this article. It is clearly written, useful and accurate. I just used your template to write a widget and it worked very well.

There is a typo in second last code sample, but it is corrected in the final code box.

$(doc).on('domloaded ajaxloaded', function($nodes) {

Should read

$(doc).on('domloaded ajaxloaded', function(nodes) {

Thanks again for the great work.

Tim Koschuetzki  said on Mar 30, 2012:

Thanks for reporting this Ben. I fixed it.

Glad you like the article. :)

Robin said on Apr 03, 2012:

How would I add public functions to this plugin?
I didn't find anything really nice until now and I like your approach.

Tim Koschuetzki  said on Apr 04, 2012:

Well you could expose your object via $.data() (see "Optional: Keeping a reference to our wrapper object in the element" in the article) and add functions there.

Alternatively you could do something like:

$.fn.widget = function(opts) {
    'init': function() {

         return this.each(function() {

           new Widget(this, opts);

         });

    },

    myFunc: function() {

      // ...

    }

  };

And access it like this from the outside:

// to init the plugin
$('.selector').widget.init();



// to access your custom function

$('.selector').widget.myFunc();
Robin said on Apr 04, 2012:

Thanks didn't quite understand that before :)
Very nice article!

Tim Koschuetzki  said on Apr 04, 2012:

@Jörn: Sorry, thought I had replied to you already.

The jQuery UI widget factory looks cool to me - I didn't know about that before. :) I think it's a great alternative to my approach. Thanks for sharing.

Ognian said on Apr 09, 2012:

Really nice work! I was trying to do something in this shape but always end up with the design shown in the jquery tutorials. Thanks for opening my eyes!

Do you mind if I translate the article in Bulgarian and publish it on my forum (with proper link to the original)?

Tim koschuetzki  said on Apr 09, 2012:

That's fine ognian, thanks. :)

Pierre Martin said on Apr 16, 2012:

Nice article!

Something I am doing more and more to "prevent designers breaking my plugin" is to use the HTML5 data-* attribute instead of classes as much as I can. It allows to make things more readable in the HTML (every data-* attribute is for the developers), and also to build the JS considering DOM elements as semantic objects with properties.

Leo Nguyen said on Apr 24, 2012:

This is very nice plugin and I found it is helpful for my project. Thank you so much. I recommended this for my colleagues.

Łukasz Lipiński said on Apr 26, 2012:

Maybe you can write something about how to deal with components on the single page application ? Do you use any sort of managers ? which tracks current context (for example which window is opened) and destroys no longer used stuff ? Example:

var ComponentManager = function() {
  "use strict";



  var context_type = null;

  var context_subtype = null;



  //Keeps instances of all registered components

  var components = {};



  //Destroys objects which contains components

  function destroy() {

   

  }



  var Module = {

    setContext : function(subtype) {

     

      return old_subtype;

    },



    //Registers component

    register : function() {

     

      return objComponent;

    },



    //Returns component instances depends on the specified arguments.

    get : function() {

      return objComponent;

    },



     //Unregisters single component

    unregister : function(name) {

     

    },



     //Its a helper function which displays registered components

    showStatus : function() {

   

    }

  };



  return Module;

};

BTW, you didn't write anything about defining your own events, did you forget ? or you don't like them ?:

jQuery.fn.button = function(params) {
  "use strict";



  var settings = $.extend({

   

  }, params);



  var _self = this, $el = $(this);



  function bindEvents() {

    $el.on('click.button touchend.button', function(e) {

      if (settings.disabled) {

        return;

      }



      _self.trigger("btn:click", [_self]);

    });

  }



  this.disable = function() {

    return this;

  };



  this.destroy = function() {



  };



  var initialize = function() {

   

  };



  initialize();



  return this;

};

This approach gives possibility to do:

var component = $("#component").button({caption : ''Some caption'}).on("btn:click", function() {
     //do something when user will click on the button

});

(to prevent unnecessary comments - I know, my example won't work if selector will return more nodes, but so far I've never had situation when I wanted to initiate multiple components at once)

Thanks for sharing your knowledge, I am looking for more articles :)
Regards

This post is too old. We do not allow comments here anymore in order to fight spam. If you have real feedback or questions for the post, please contact us.