A Convenient Way to Write a jQuery Plugin

This article was originally published in my blog (affectionately referred to as blargh) on . The original blog no longer exists as I've migrated everything to this wiki.

The original URL of this post was at https://tmont.com/blargh/2014/1/a-convenient-way-to-write-a-jquery-plugin. Hopefully that link redirects back to this page.

I've written quite a few jQuery plugins in my day. The early ones were horrible mishmashes of spaghetti code interlaced with $.fn. Back then I didn't even know $.fn was supposed to be. I thought fn was just some magic string that did something magical, and then you suddenly had written a jQuery plugin!

Since then, for decently sophisticated plugins (i.e. ones that create state) I've started constructing them in a more consistent manner. More specifically, I totally ripped off the format that the Bootstrap authors used for their plugins, like $('selector').button('reset'). I had to hack the button plugin because for some reason it put every action in the event loop with setTimeout and I couldn't rely on things occurring in the proper order. As I was cursing the authors out for doing something so annoying, I was simultaneously praising them for writing their plugins in a way that was consistent and easy to work with. Even if they don't use semicolons. The heathen bastards.

Before I get to the actual format, I want to emphasize that for simple plugins, doing things like this is fine (and probably preferable):

javascript
$.fn.redOrBlueLol = function() {
  return this.each(function() {
    var $this = $(this);
    $this.css('color', $this.css('color') === 'red' ? 'blue' : 'red');
  });
};

If all you're doing is some minor DOM manipulation, then you don't really need to worry about writing your plugin in some consistent format.

The Format

First, you have your stateful object and its prototype:

javascript
var ns = 'my-plugin';
function MyStatefulObject($element, options) {
  this.$element = $element;
  this.doStuff = !!options.doStuff;
}

MyStatefulObject.prototype = {
  helloWorld: function(name) {
    name = name || 'world';
    this.$element.text('Hello ' + name + '!');
  },
  
  destroy: function() {
    // stuff to tear down all the mess you've made
    this.$element.off('.' + ns);
  }
};

Then the actual definition of the plugin:

javascript
$.fn.myPlugin = function(options) {
  options = options || {};
  return this.each(function() {
    var $element = $(this),
        thing = $element.data(ns),
        method = typeof(options) === 'string' ? options : '';
    
    if (!thing) {
      var realOptions = !options || typeof(options) !== 'object' ? {} : options;
      $element.data(ns, (thing = new MyStatefulObject($element, realOptions));
    }
    
    if (typeof(thing[method]) === 'function') {
      thing[method].apply(thing, [].slice.call(arguments, 1));
    }
  });
};

And that's it. Now you can use your plugin as such:

javascript
$('selector').myPlugin('helloWorld');
$('.billy').myPlugin('helloWorld', 'Billy');
$('selector, .billy').myPlugin('destroy');

You can see an example of this in action in an audio player plugin I've been working lately: jquery.rach3.js

And that's it. Nothing very complicated or magical about it. Just convenient.