Starter Template for Creating a jQuery Plugin

18. January 2012

Every time I needed to create a jQuery plugin, I would debate with myself on how to structure the code. It occurred to me that it would be helpful to stub out a sample plugin template that I could grab and jump right into coding.

After looking around at various jQuery plugin patterns, I came up with the template at the end of this post. I think it has a nice structure, but would enjoy hearing any suggestions. The one thing I may add to this in the future is the ability to programmatically uninstall the plugin.

Here are some samples of how you might invoke the plugin and then call it's API.

// Install the plugin on all matching elements
$("div.myPlugin").myPlugin();

// Install the plugin and override a default setting
$("div.myPlugin").myPlugin({
    playPauseSelector:'#playbutton'
});

// Control multiple instances at once 
//  and also maintain the fluent API using
//  the jQuery plugin function to call the API
$("div.myPlugin").myPlugin("play", 2); 
$("div.myPlugin").myPlugin("play", 2).myPlugin("pause"); 

// Control a single instance by accessing the attached API
// Only the officially supported API methods can be called
$("div.myPlugin").get(0).MyPlugin.play(2); 

Here is the code:

NOTE: Some parts of the code are included only to illustrate aspects of the structure. See the code comments for more explanation.

(function ($) {

    // Create the plugin jQuery function
    $.fn.myPlugin = function (optionsOrCommand, param) {

        // Setup the default options
        var defaultOptions = {
            playPauseSelector: '.MyPluginPlayPauseButton',
            playingClass: 'playing',
            pausedClass: 'paused'
        };
        
        // Define a function to setup the plugin instance
        var create = function (root, options) {
 
            // Grab a reference to the root DOM element that is the target of the plugin
            var _root = $(root);
            var _playPauseButton = null;

            // Update the settings with any overrides
            var _opts = $.extend({}, defaultOptions, (options || {}));
            
            // Create internal helper methods
            var _play = function(idx) {
                _playPauseButton.removeClass(pausedClass).addClass(playingClass);
            };
            
            var _pause = function() {
                _playPauseButton.removeClass(playingClass).addClass(pausedClass);
            };
            
            // Define an init function to kick off the plugin functionality
            var _init = function () {
                _playPauseButton = $(playPauseSelector, _root);
                _playPauseButton.bind('click', function() {
                    var self = $(this);
                    if (self.hasClass(playingClass)) {
                        _pause();
                    } else {
                        _play();
                    }
                });
            };
            
            // Return an object containing a reference to the API and the init method
            // We don't want the init in the API so it is separated here
            return {
                controller: {
                    init: function () {
                        _init();
                    }
                },
                api: {
                    pause: function () {
                        _pause();
                    },
                    play: function (idx) {
                        _play(idx);
                    }
                }
            };
        };
        
        // Cycle through each matching element and return them  
        // so as not to break the jQuery fluent API
        return this.each(function () {

            // Grab a reference to the root element
            var root = this;

            // Determine whether or not the plugin is already installed
            var exists = !(root.MyPlugin === undefined || root.MyPlugin === null);

            // Initialize the optionsOrCommand so we can handle either input
            if (optionsOrCommand === undefined || optionsOrCommand === null) {
                optionsOrCommand = {};
            }

            // If the optionsOrCommand is a string then map it to the API
            if (optionsOrCommand.constructor == String) {
                if (exists) {
                    switch (optionsOrCommand) {
                        case 'pause':
                        case 'play':
                            if (typeof root.MyPlugin[optionsOrCommand] === 'function') {
                                    root.MyPlugin[optionsOrCommand](param);
                            }
                            break;
                        default:
                            return;
                    }
                }
                return;
            }

            // If the plugin is not installed then create it and apply the API
            // to the DOM element.
            if (!exists) {
                var plugin = create(root, optionsOrCommand);
                root.MyPlugin = plugin.api;
                plugin.controller.init();
            }
        });
    };
})(jQuery);

Here are some links to other interesting plugin patterns:
JQUERY GENERATOR BY Kees C. Bakker
Creating a jQuery plugin
Keeping the plugin and jQuery separate

jQuery, Programming , ,

A jQuery action plugin to insert a custom function into the method chain

19. May 2010

I was hanging out at the #jquery IRC Channel through freenode Web IRC and a user wanted to have a callback function on the removeClass method.  This seems like a strange request since the removeClass method is a synchronous call and returns the list of jQuery items which were manipulated.

The problem could be solved as follows:

var items = $(".target").removeClass("target");
doSomething();
items.addClass("newTarget");

However the user was not satisfied with this option, so I suggested adding a callback to the removeClass method like so:

(function($) {
  var oldRemoveClass = $.fn.removeClass;
  $.fn.removeClass = function(value, callback) {
    var ret = oldRemoveClass.call(this, value);
    if ($.isFunction(callback)) {
      callback.call(ret);
    }
    return ret;
  }
})(jQuery);

NOTE: A sample is available on JS Bin.

However, after some consideration, I thought of a another option (again, of questionable value, but hey...) Why not add a jQuery function that takes a callback and returns the fluent context. The code would be quite similar to the above but would apply more broadly.

Plugin code

/*Final plugin*/
(function($) {
  $.fn.action = function(callback) {
    var ret = this;
    if ($.isFunction(callback)) {
      callback.call(ret);
    }
    return ret;
  }
})(jQuery);

Now with the new function in place the user can string a custom function into the method chain. The original solution would be refactored to look like this.

$(".target").removeClass("target").action(doSomething).addClass("newTarget");

The action plugin could allow the callback to filter or replace the item context, but I think that would hurt readability and lead to hidden bugs.

jQuery, Programming , , ,