Skip to content

jQuery Plugin Guide

ScottM edited this page May 29, 2015 · 3 revisions

Overview

This document was originally written in late 2012.

This document attempts to capture the core aspects of writing "good" jQuery plugins.

Note: There are many different versions of "good".

The main points are:

  • Create a JS file for each plugin, rather than something like functions.js (maintenence).
  • Bind against a class selector (performance, flexibility).
  • Use "fn-" prefix (E.g. fn-myPlugin) to distinguish this class against presentation classes.
  • Wrap your plugin code in a closure (prevent pollution of global scope).
  • Pass in top-level objects, mainly "window" (performance).
  • Pass in undefined (which is an object and thus mutable, so we want it to always BE undefined).
  • Use strict mode to avoid common bugs (like assigning variables to global scope by leaving off var).
  • Use constructor invocation pattern.
  • Extend object prototype with "methods" (aka functions) (performance).
  • (Contended) Bind your plugin instance to the element using data(). (debugging, monkey-patching).
  • Make use of options/defaults for customising behaviour.
  • Make use of data-attributes for per-instance customisation (like data-url).
  • Make liberal use of whitespace (it's free!).
  • Write Jasmine tests as you go, for TDD! Helps you think about your API and objectives too.
  • Use JSHint to check your code. These tools can be integrated into your editor, too.
  • Use the "one true brace" style, which avoids the "semicolon insertion" bug.

Boilerplate Code

The following is the bare-minimum to get an idea of the code/syntax:

/**
 * "myPlugin" jQuery plugin.
 *
 * Here is a description of what my plugin does and why.
 *
 * Include examples, and list the various options that can be passed in.
 *
 * @author Scott Maclure <[email protected]>
 */
(function (window, undefined) {
 
    "use strict";
 
    var $ = window.jQuery;
    var console = window.console;
 
    var defaults = {
        "someFlag": true,
        "someHtml": "<p>Snippet of HTML!</p>"
    };
 
    /**
     * Constructor.
     *
     * @param {Object} element DOM Element plugin is bound against
     * @param {Object} options Options
     */
    function MyPlugin(element, options) {
 
        // Cache (jQuery wrapped) reference to element this plugin binds against.
        this.$element = $(element);
 
        // Override defaults with passed options.
        this.options = $.extend({}, defaults, options);
 
        // Common scenario
        this.bindEvents();
    }
 
    // Override prototype object with methods.
    // Use extend to preserve constructor.
    MyPlugin.prototype = $.extend(MyPlugin.prototype, {
 
        /**
         * Called from constructor.
         * Good example of using "self" inside a closure.
         */
        bindEvents: function () {
 
            var self = this;
 
            this.$element.on("click", "a.myPluginClickableElement", function () {
                self.doSomethingElse($(this).text());
            });
 
        },
 
        /**
         * Example of another method.
         * @param {String} someVar Some text to output to console.
         */
        doSomethingElse: function (someVar) {
            console.log("someVar: " + someVar);
        }
 
    };
 
    // Create function for plugin in jQuery "fn" namespace.
    $.fn.myPlugin = function (options) {
 
        // For chainability, return jQuery object.
        return this.each(function () {
            $(this).data('myPlugin', new MyPlugin(this, options));
        });
 
    };
 
})(window);

And then somewhere else where you bind plugins:

$(document).ready(function(){
 
    // Bind against a class selector (performance, flexibility)
    $(".fn-myPlugin").myPlugin({
        "someHtml": "<p>Some other html! Override!</p>"
    });
 
});

Global definitions for jshint

If a global object is expected, you can avoid jshint complaining by adding this to the first line in your JS file:

/*global SomeGlobal*/

You really want to pass your dependencies into your closure. Good practice.

Technical design issues with writing plugins

What is a plugin?

The short answer: something that will be attached to a DOM element to perform some UI-centric tasks for you. It will be part of the jQuery "fn namespace", and activated like:

$(document).ready(function () {
    $(".fn-myPlugin").myPlugin();
});

Any other code (like helper functions) can be abstracted to "utils" files and loaded before the plugin, so the plugin can make use of it.

Can two plugins be used on the same element?

That's a tricky one. Be careful, you can get strange behaviours and you can end up writing custom code inside the plugins to handle those situations.

If you are in that situation, a good solution in the short-term is to emit generically named events which plugins can listen for. This allows you to break up your code's execution into discrete steps, which start/end with an event. You then find you can combine plugins in really nice ways.

Another approach is to abstract common behaviour in the plugins into helper/utility objects, and then create a new plugin which is a kind of "hybrid" of the other two. That leads into Blake's "behaviours" design, however, which separates "plugins" into two types of objects: "behaviours" (which are attached to DOM) and "interfaces" (which are used by behaviours, and represent generic functionality used by behaviours). But that's another topic.

Try to keep your plugins small in scope

Rather than creating monolithic plugins that do everything for you (which leads to violation of DRY principle), try to break them down to some sensible "building block" level. This allows you to combine plugins on various elements in your DOM (which is a huge benefit), and it keeps your plugin files small and testable.

The key is emitting/listening for events in your plugins. Because we don't use a "Pub Sub" library at present, you can emit an event on your DOM element, and listen at window, or as appropriate.

For example, in a previous project we built some fairly involved "admin UI" using these kind of plugins.

One such example is how we used an "ajax loader" plugin with an "overlay" plugin, so when an admin clicks some "edit" icon, they load an interface into an overlay via an ajax call.

External Resources

Probably a bit out-of-date, but a good read:

http://docs.jquery.com/Plugins/Authoring

Good points here:

http://debuggable.com/posts/how-to-write-jquery-plugins:4f72ab2e-7310-4a74-817a-0a04cbdd56cb

jQuery widget factory:

http://wiki.jqueryui.com/w/page/12138135/Widget%20factory