JavaScript-jQuery Plugin Design Patterns - 书目录

JavaScript-jQuery Plugin Design Patterns

jQuery Plugin Design Patterns

jQuery plugin development has evolved over the past few years. We no longer have just one way to write plugins, but many. In reality, certain plugin design patterns might work better for a particular problem or component than others.

Some developers may wish to use the jQuery UI widget factory; it’s great for complex, flexible UI components. Some may not.

Some might like to structure their plugins more like modules (similar to the module pattern) or use a more modern module format such as AMD.

Some might want their plugins to harness the power of prototypal inheritance. Others may wish to use custom events or Publish/Subscribe to communicate from plugins to the rest of their app. And so on.

I began to think about plugin patterns after noticing a number of efforts to create a one-size-fits-all jQuery plugin boilerplate. While such a boilerplate is a great idea in theory, the reality is that we rarely write plugins in one fixed way, using a single pattern all the time.

Let us assume that we’ve tried our hand at writing our own jQuery plugins at some point and we’re comfortable putting together something that works. It’s functional. It does what it needs to do, but perhaps we feel it could be structured better. Maybe it could be more flexible or could be designed to address more of the issues developers commonly run into. If this sounds familiar, then you might find this chapter useful. In it, we’re going to explore a number of jQuery plugin patterns that have worked well for other developers in the wild.

Note: This chapter is targeted at intermediate to advanced developers, although we will briefly review some jQuery plugin fundamentals to begin.

If you don’t feel quite ready for this just yet, I’m happy to recommend the official jQuery Plugins/Authoring guide, Ben Alman’s plugin style guide and Remy Sharp’s “Signs of a Poorly Written jQuery Plugin.” as reading material prior to starting this section.

Patterns

jQuery plugins have few concrete rules, which is one of the reasons for the incredible diversity in how they are implemented across the community. At the most basic level, we can write a plugin simply by adding a new function property to jQuery’s jQuery.fn object, as follows:

1
2
3
$.fn.myPluginName = function () {
    // our plugin logic
};

This is great for compactness, but the following would be a better foundation to build on:

1
2
3
4
5
(function( $ ){
  $.fn.myPluginName = function () {
    // our plugin logic
  };
})( jQuery );

Here, we’ve wrapped our plugin logic in an anonymous function. To ensure that our use of the $ sign as a shorthand creates no conflicts between jQuery and other JavaScript libraries, we simply pass it to this closure, which maps it to the dollar sign. This ensures that it can’t be affected by anything outside of its scope of execution.

An alternative way to write this pattern would be to use jQuery.extend(), which enables us to define multiple functions at once and which sometimes make more sense semantically:

1
2
3
4
5
6
7
(function( $ ){
    $.extend($.fn, {
        myplugin: function(){
            // your plugin logic
        }
    });
})( jQuery );

We have now reviewed some jQuery plugin fundamentals, but a lot more could be done to take this further. A Lightweight Start is the first complete plugin design pattern we’ll be exploring and it covers some best practices that we can use for basic everyday plugin development, taking into account common gotchas worth applying.

Note

While most of the patterns below will be explained, I recommend reading through the comments in the code, because they will offer more insight into why certain best practices are applied.

I should also mention that none of this would be possible without the previous work, input and advice of other members of the jQuery community. I’ve listed them inline with each pattern so that one can read up on their individual work if interested.

 

 

‘A Lightweight Start’ Pattern

Let’s begin our deeper look at plugin patterns with something basic that follows best practices (including those in the jQuery plugin-authoring guide). This pattern is ideal for developers who are either new to plugin development or who just want to achieve something simple (such as a utility plugin). A Lightweight Start uses the following:

  • Common best practices such as a semi-colon placed before the functions invocation (we’ll go through why in the comments below)
  • window, document, undefined passed in as arguments.
  • A basic defaults object.
  • A simple plugin constructor for logic related to the initial creation and the assignment of the element to work with.
  • Extending the options with defaults.
  • A lightweight wrapper around the constructor, which helps to avoid issues such as multiple instantiations.
  • Adherence to the jQuery core style guidelines for maximized readability.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*!
 * jQuery lightweight plugin boilerplate
 * Original author: @ajpiano
 * Further changes, comments: @addyosmani
 * Licensed under the MIT license
 */
// the semi-colon before the function invocation is a safety
// net against concatenated scripts and/or other plugins
// that are not closed properly.
;(function ( $, window, document, undefined ) {
    // undefined is used here as the undefined global
    // variable in ECMAScript 3 and is mutable (i.e. it can
    // be changed by someone else). undefined isn’t really
    // being passed in so we can ensure that its value is
    // truly undefined. In ES5, undefined can no longer be
    // modified.
    // window and document are passed through as local
    // variables rather than as globals, because this (slightly)
    // quickens the resolution process and can be more
    // efficiently minified (especially when both are
    // regularly referenced in our plugin).
    // Create the defaults once
    var pluginName = “defaultPluginName”,
        defaults = {
            propertyName: “value”
        };
    // The actual plugin constructor
    function Plugin( element, options ) {
        this.element = element;
        // jQuery has an extend method that merges the
        // contents of two or more objects, storing the
        // result in the first object. The first object
        // is generally empty because we don’t want to alter
        // the default options for future instances of the plugin
        this.options = $.extend( {}, defaults, options) ;
        this._defaults = defaults;
        this._name = pluginName;
        this.init();
    }
    Plugin.prototype.init = function () {
        // Place initialization logic here
        // We already have access to the DOM element and
        // the options via the instance, e.g. this.element
        // and this.options
    };
    // A really lightweight plugin wrapper around the constructor,
    // preventing against multiple instantiations
    $.fn[pluginName] = function ( options ) {
        return this.each(function () {
            if ( !$.data(this, “plugin_” + pluginName )) {
                $.data( this, “plugin_” + pluginName,
                new Plugin( this, options ));
            }
        });
    }
})( jQuery, window, document );

Usage:

1
2
3
$(“#elem”).defaultPluginName({
  propertyName: “a custom value”
});

Further Reading

 

 

“Complete” Widget Factory Pattern

While the jQuery plugin authoring guide is a great introduction to plugin development, it doesn’t help obscure away common plugin plumbing tasks that we have to deal with on a regular basis.

The jQuery UI Widget Factory is a solution to this problem that helps us build complex, stateful plugins based on object-oriented principles. It also eases communication with our plugins instance, obfuscating a number of the repetitive tasks that we would have to code when working with basic plugins.

Stateful plugins help us keep track of their current state, also allowing us to change properties of the plugin after it has been initialized.

One of the great things about the Widget Factory is that the majority of the jQuery UI library actually uses it as a base for its components. This means that if we’re looking for further guidance on structure beyond this pattern, we won’t have to look beyond the jQuery UI repository on GitHub (https://github.com/jquery/jquery-ui).

This jQuery UI Widget Factory pattern covers almost all of the supported default factory methods, including triggering events. As per the last pattern, comments are included for all of the methods used and further guidance is given in the inline comments.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/*!
 * jQuery UI Widget-factory plugin boilerplate (for 1.8/9+)
 * Author: @addyosmani
 * Further changes: @peolanha
 * Licensed under the MIT license
 */
;(function ( $, window, document, undefined ) {
    // define our widget under a namespace of your choice
    // with additional parameters e.g.
    // $.widget( “namespace.widgetname”, (optional) – an
    // existing widget prototype to inherit from, an object
    // literal to become the widget’s prototype );
    $.widget( “namespace.widgetname”, {
        //Options to be used as defaults
        options: {
            someValue: null
        },
        //Setup widget (e.g. element creation, apply theming
        //, bind events etc.)
        _create: function () {
            // _create will automatically run the first time
            // this widget is called. Put the initial widget
            // setup code here, then we can access the element
            // on which the widget was called via this.element.
            // The options defined above can be accessed
            // via this.options this.element.addStuff();
        },
        // Destroy an instantiated plugin and clean up
        // modifications the widget has made to the DOM
        destroy: function () {
            // this.element.removeStuff();
            // For UI 1.8, destroy must be invoked from the
            // base widget
            $.Widget.prototype.destroy.call( this );
            // For UI 1.9, define _destroy instead and don’t
            // worry about
            // calling the base widget
        },
        methodB: function ( event ) {
            //_trigger dispatches callbacks the plugin user
            // can subscribe to
            // signature: _trigger( “callbackName”, [eventObject],
            // [uiObject] )
            // e.g. this._trigger( “hover”, e /*where e.type ==
            // “mouseenter”*/, { hovered: $(e.target)});
            this._trigger( “methodA”, event, {
                key: value
            });
        },
        methodA: function ( event ) {
            this._trigger( “dataChanged”, event, {
                key: value
            });
        },
        // Respond to any changes the user makes to the
        // option method
        _setOption: function ( key, value ) {
            switch ( key ) {
            case “someValue”:
                // this.options.someValue = doSomethingWith( value );
                break;
            default:
                // this.options[ key ] = value;
                break;
            }
            // For UI 1.8, _setOption must be manually invoked
            // from the base widget
            $.Widget.prototype._setOption.apply( this, arguments );
            // For UI 1.9 the _super method can be used instead
            // this._super( “_setOption”, key, value );
        }
    });
})( jQuery, window, document );

Usage:

1
2
3
4
5
var collection = $(“#elem”).widgetName({
  foo: false
});
collection.widgetName(“methodB”);

Further Reading

 

 

Nested Namespacing Plugin Pattern

As we’ve previously covered in the book, namespacing our code is a way to avoid collisions with other objects and variables in the global namespace. They’re important because we want to safeguard our plugin from breaking in the event that another script on the page uses the same variable or plugin names as ours. As a good citizen of the global namespace, we must also do our best not to prevent other developers scripts from executing because of the same issues.

JavaScript doesn’t really have built-in support for namespaces as other languages do, but it does have objects that can be used to achieve a similar effect. Employing a top-level object as the name of our namespace, we can easily check for the existence of another object on the page with the same name. If such an object does not exist, then we define it; if it does exist, then we simply extend it with our plugin.

Objects (or, rather, object literals) can be used to create nested namespaces, such as namespace.subnamespace.pluginName and so on. But to keep things simple, the namespacing boilerplate below should show us everything we need to get started with these concepts.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/*!
 * jQuery namespaced “Starter” plugin boilerplate
 * Author: @dougneiner
 * Further changes: @addyosmani
 * Licensed under the MIT license
 */
;(function ( $ ) {
    if (!$.myNamespace) {
        $.myNamespace = {};
    };
    $.myNamespace.myPluginName = function ( el, myFunctionParam, options ) {
        // To avoid scope issues, use “base” instead of “this”
        // to reference this class from internal events and functions.
        var base = this;
        // Access to jQuery and DOM versions of element
        base.$el = $( el );
        base.el = el;
        // Add a reverse reference to the DOM object
        base.$el.data( “myNamespace.myPluginName”, base );
        base.init = function () {
            base.myFunctionParam = myFunctionParam;
            base.options = $.extend({},
            $.myNamespace.myPluginName.defaultOptions, options);
            // Put our initialization code here
        };
        // Sample Function, Uncomment to use
        // base.functionName = function( parameters ){
        //
        // };
        // Run initializer
        base.init();
    };
    $.myNamespace.myPluginName.defaultOptions = {
        myDefaultValue: “”
    };
    $.fn.mynamespace_myPluginName = function
        ( myFunctionParam, options ) {
        return this.each(function () {
            (new $.myNamespace.myPluginName( this,
            myFunctionParam, options ));
        });
    };
})( jQuery );

Usage:

1
2
3
$(“#elem”).mynamespace_myPluginName({
  myDefaultValue: “foobar”
});

Further Reading

 

 

Custom Events Plugin Pattern (With The Widget factory)

In the JavaScript Design Patterns section of the book, we discussed the Observer pattern and later went on to cover jQuery’s support for custom events, which offer a similar solution for implementing Publish/Subscribe. This same pattern can be used when writing jQuery plugins.

The basic idea here is that objects in a page can publish event notifications when something interesting occurs in our application. Other objects then subscribe to (or listen) for these events and respond accordingly. This results in the logic for our application being significantly more decoupled, as each object no longer needs to directly communicate with another.

In the following jQuery UI widget factory pattern, we’ll implement a basic custom event-based Publish/Subscribe system that allows our plugin to subscribe to event notifications from the rest of our application, which will be responsible for publishing them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*!
 * jQuery custom-events plugin boilerplate
 * Author: DevPatch
 * Further changes: @addyosmani
 * Licensed under the MIT license
 */
// In this pattern, we use jQuery’s custom events to add
// pub/sub (publish/subscribe) capabilities to widgets.
// Each widget would publish certain events and subscribe
// to others. This approach effectively helps to decouple
// the widgets and enables them to function independently.
;(function ( $, window, document, undefined ) {
    $.widget( “ao.eventStatus”, {
        options: {
        },
        _create: function() {
            var self = this;
            //self.element.addClass( “my-widget” );
            //subscribe to “myEventStart”
            self.element.on( “myEventStart”, function( e ) {
                console.log( “event start” );
            });
            //subscribe to “myEventEnd”
            self.element.on( “myEventEnd”, function( e ) {
                console.log( “event end” );
            });
            //unsubscribe to “myEventStart”
            //self.element.off( “myEventStart”, function(e){
                ///console.log( “unsubscribed to this event” );
            //});
        },
        destroy: function(){
            $.Widget.prototype.destroy.apply( this, arguments );
        },
    });
})( jQuery, window, document );
// Publishing event notifications
// $( “.my-widget” ).trigger( “myEventStart”);
// $( “.my-widget” ).trigger( “myEventEnd” );

Usage:

1
2
3
var el = $( “#elem” );
el.eventStatus();
el.eventStatus().trigger( “myEventStart” );

Further Reading

 

 

Prototypal Inheritance With The DOM-To-Object Bridge Pattern

As covered earlier, in JavaScript, we don’t have the traditional notion of classes that we would find in other classical programming languages, but we do have prototypal inheritance. With prototypal inheritance, an object inherits from another object. We can apply this concept to jQuery plugin development.

Yepnope.js author Alex Sexton and jQuery team member Scott Gonzalez have looked at this topic in detail. In sum, they discovered that for organized modular development, clearly separating the object that defines the logic for a plugin from the plugin-generation process itself can be beneficial.

The benefit is that testing our plugins code becomes significantly easier and we are also able to adjust the way things work behind the scenes without altering the way that any object APIs we implement are used.

In Sexton’s article on this topic, he implemented a bridge that enables us to attach our general logic to a particular plugin, which we’ve implemented in the pattern below.

One of the other advantages of this pattern is that we don’t have to constantly repeat the same plugin initialization code, thus ensuring that the concepts behind DRY development are maintained. Some developers might also find this pattern easier to read than others.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/*!
 * jQuery prototypal inheritance plugin boilerplate
 * Author: Alex Sexton, Scott Gonzalez
 * Further changes: @addyosmani
 * Licensed under the MIT license
 */
// myObject – an object representing a concept we wish to model
// (e.g. a car)
var myObject = {
  init: function( options, elem ) {
    // Mix in the passed-in options with the default options
    this.options = $.extend( {}, this.options, options );
    // Save the element reference, both as a jQuery
    // reference and a normal reference
    this.elem = elem;
    this.$elem = $( elem );
    // Build the DOM’s initial structure
    this._build();
    // return this so that we can chain and use the bridge with less code.
    return this;
  },
  options: {
    name: “No name”
  },
  _build: function(){
    //this.$elem.html( “<h1>”+this.options.name+”</h1>” );
  },
  myMethod: function( msg ){
    // We have direct access to the associated and cached
    // jQuery element
    // this.$elem.append( “<p>”+msg+”</p>” );
  }
};
// Object.create support test, and fallback for browsers without it
if ( typeof Object.create !== “function” ) {
    Object.create = function (o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}
// Create a plugin based on a defined object
$.plugin = function( name, object ) {
  $.fn[name] = function( options ) {
    return this.each(function() {
      if ( ! $.data( this, name ) ) {
        $.data( this, name, Object.create( object ).init(
        options, this ) );
      }
    });
  };
};

Usage:

1
2
3
4
5
6
$.plugin( “myobj”, myObject );
$(“#elem”).myobj( {name: “John”} );
var collection = $( “#elem” ).data( “myobj” );
collection.myMethod( “I am a method”);

Further Reading

 

 

jQuery UI Widget Factory Bridge Pattern

If you liked the idea of generating plugins based on objects in the last design pattern, then you might be interested in a method found in the jQuery UI Widget Factory called $.widget.bridge.

This bridge basically serves as a middle layer between a JavaScript object that is created using $.widgetand the jQuery core API, providing a more built-in solution to achieving object-based plugin definition. Effectively, we’re able to create stateful plugins using a custom constructor.

Moreover,$.widget.bridge provides access to a number of other capabilities, including the following:

  • Both public and private methods are handled as one would expect in classical OOP (i.e. public methods are exposed, while calls to private methods are not possible).
  • Automatic protection against multiple initializations.
  • Automatic generation of instances of a passed object, and storage of them within the selection’s internal $.data cache.
  • Options can be altered post-initialization.

For further information on how to use this pattern, please see the inline comments below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/*!
 * jQuery UI Widget factory “bridge” plugin boilerplate
 * Author: @erichynds
 * Further changes, additional comments: @addyosmani
 * Licensed under the MIT license
 */
// a “widgetName” object constructor
// required: this must accept two arguments,
// options: an object of configuration options
// element: the DOM element the instance was created on
var widgetName = function( options, element ){
  this.name = “myWidgetName”;
  this.options = options;
  this.element = element;
  this._init();
}
// the “widgetName” prototype
widgetName.prototype = {
    // _create will automatically run the first time this
    // widget is called
    _create: function(){
        // creation code
    },
    // required: initialization logic for the plugin goes into _init
    // This fires when our instance is first created and when
    // attempting to initialize the widget again (by the bridge)
    // after it has already been initialized.
    _init: function(){
        // init code
    },
    // required: objects to be used with the bridge must contain an
    // “option”. Post-initialization, the logic for changing options
    // goes here.
    option: function( key, value ){
        // optional: get/change options post initialization
        // ignore if you don’t require them.
        // signature: $(“#foo”).bar({ cool:false });
        if( $.isPlainObject( key ) ){
            this.options = $.extend( true, this.options, key );
        // signature: $( “#foo” ).option( “cool” ); – getter
        } else if ( key && typeof value === “undefined” ){
            return this.options[ key ];
        // signature: $( “#foo” ).bar(“option”, “baz”, false );
        } else {
            this.options[ key ] = value;
        }
        // required: option must return the current instance.
        // When re-initializing an instance on elements, option
        // is called first and is then chained to the _init method.
        return this;
    },
    // notice no underscore is used for public methods
    publicFunction: function(){
        console.log( “public function” );
    },
    // underscores are used for private methods
    _privateFunction: function(){
        console.log( “private function” );
    }
};

Usage:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// connect the widget obj to jQuery’s API under the “foo” namespace
$.widget.bridge( “foo”, widgetName );
// create an instance of the widget for use
var instance = $( “#foo” ).foo({
   baz: true
});
// our widget instance exists in the elem’s data
// Outputs: #elem
console.log(instance.data( “foo” ).element);
// bridge allows us to call public methods
// Outputs: “public method”
instance.foo(“publicFunction”);
// bridge prevents calls to internal methods
instance.foo(“_privateFunction”);

Further Reading

 

 

jQuery Mobile Widgets With The Widget factory

jQuery mobile is a jQuery project framework that encourages the design of ubiquitous web applications that work both on popular mobile devices and platforms and on the desktop. Rather than writing unique applications for each device or OS, we simply write the code once and it should ideally run on many of the A-, B- and C-grade browsers out there at the moment.

The fundamentals behind jQuery mobile can also be applied to plugin and widget development.

What’s interesting in this next pattern is that although there are small, subtle differences in writing a “mobile”-optimized widget, those familiar with using the jQuery UI Widget Factory pattern from earlier should be able to grasp this in next to no time.

The mobile-optimized widget below has a number of interesting differences than the standard UI widget pattern we saw earlier:

  • $.mobile.widget is referenced as an existing widget prototype from which to inherit. For standard widgets, passing through any such prototype is unnecessary for basic development, but using this jQuery-mobile specific widget prototype provides internal access to further “options” formatting.
  • In _create(), a guide is provided on how the official jQuery mobile widgets handle element selection, opting for a role-based approach that better fits the jQM mark-up. This isn’t at all to say that standard selection isn’t recommended, only that this approach might make more sense given the structure of jQuery Mobile pages.
  • Guidelines are also provided in comment form for applying our plugin methods on pagecreate as well as for selecting the plugin application via data roles and data attributes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/*!
 * (jQuery mobile) jQuery UI Widget-factory plugin boilerplate (for 1.8/9+)
 * Author: @scottjehl
 * Further changes: @addyosmani
 * Licensed under the MIT license
 */
;(function ( $, window, document, undefined ) {
    // define a widget under a namespace of our choice
    // here “mobile” has been used in the first argument
    $.widget( “mobile.widgetName”, $.mobile.widget, {
        // Options to be used as defaults
        options: {
            foo: true,
            bar: false
        },
        _create: function() {
            // _create will automatically run the first time this
            // widget is called. Put the initial widget set-up code
            // here, then we can access the element on which
            // the widget was called via this.element
            // The options defined above can be accessed via
            // this.options
            // var m = this.element,
            // p = m.parents( “:jqmData(role=”page”)” ),
            // c = p.find( “:jqmData(role=”content”)” )
        },
        // Private methods/props start with underscores
        _dosomething: function(){ … },
        // Public methods like these below can can be called
        // externally:
        // $(“#myelem”).foo( “enable”, arguments );
        enable: function() { … },
        // Destroy an instantiated plugin and clean up modifications
        // the widget has made to the DOM
        destroy: function () {
            // this.element.removeStuff();
            // For UI 1.8, destroy must be invoked from the
            // base widget
            $.Widget.prototype.destroy.call( this );
            // For UI 1.9, define _destroy instead and don’t
            // worry about calling the base widget
        },
        methodB: function ( event ) {
            //_trigger dispatches callbacks the plugin user can
            // subscribe to
            // signature: _trigger( “callbackName”, [eventObject],
            // [uiObject] )
            // e.g. this._trigger( “hover”, e /*where e.type ==
            // “mouseenter”*/, { hovered: $(e.target)});
            this._trigger( “methodA”, event, {
                key: value
            });
        },
        methodA: function ( event ) {
            this._trigger( “dataChanged”, event, {
                key: value
            });
        },
        // Respond to any changes the user makes to the option method
        _setOption: function ( key, value ) {
            switch ( key ) {
            case “someValue”:
                // this.options.someValue = doSomethingWith( value );
                break;
            default:
                // this.options[ key ] = value;
                break;
            }
            // For UI 1.8, _setOption must be manually invoked from
            // the base widget
            $.Widget.prototype._setOption.apply(this, arguments);
            // For UI 1.9 the _super method can be used instead
            // this._super( “_setOption”, key, value );
        }
    });
})( jQuery, window, document );

Usage:

1
2
3
4
5
var instance = $( “#foo” ).widgetName({
  foo: false
});
instance.widgetName( “methodB” );

We can also self-initialize this widget whenever a new page in jQuery Mobile is created. jQuery Mobile’s page plugin dispatches a create event when a jQuery Mobile page (found via the data-role=”page”attribute) is first initialized. We can listen for that event (called “pagecreate”) and run our plugin automatically whenever a new page is created.

1
2
3
4
5
6
7
8
9
10
11
12
13
$(document).on(“pagecreate”, function ( e ) {
    // In here, e.target refers to the page that was created
    // (it’s the target of the pagecreate event)
    // So, we can simply find elements on this page that match a
    // selector of our choosing, and call our plugin on them.
    // Here’s how we’d call our “foo” plugin on any element with a
    // data-role attribute of “foo”:
    $(e.target).find( “[data-role=”foo”]” ).foo( options );
    // Or, better yet, let’s write the selector accounting for the configurable
    // data-attribute namespace
    $( e.target ).find( “:jqmData(role=”foo”)” ).foo( options );
});

We can now simply reference the script containing our widget and pagecreate binding in a page running jQuery Mobile site, and it will automatically run like any other jQuery Mobile plugin.

 

 

RequireJS And The jQuery UI Widget Factory

As we covered in the section on Modern Module Design Patterns, RequireJS is an AMD-compatible script loader that provides a clean solution for encapsulating application logic inside manageable modules.

It’s able to load modules in the correct order (through its order plugin), simplifies the process of combining scripts via its excellent r.js optimizer and provides the means for defining dynamic dependencies on a per-module basis.

In the boilerplate pattern below, we demonstrate how an AMD (and thus RequireJS) compatible jQuery UI widget can be defined that does the following:

  • Allows the definition of widget module dependencies, building on top of the previous jQuery UI Widget Factory pattern presented earlier.
  • Demonstrates one approach to passing in HTML template assets for creating templated widgets (using Underscore.js micro-templating).
  • Includes a quick tip on adjustments that we can make to our widget module if we wish to later pass it through to the RequireJS optimizer.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/*!
 * jQuery UI Widget + RequireJS module boilerplate (for 1.8/9+)
 * Authors: @jrburke, @addyosmani
 * Licensed under the MIT license
 */
// Note from James:
//
// This assumes we are using the RequireJS+jQuery file, and
// that the following files are all in the same directory:
//
// – require-jquery.js
// – jquery-ui.custom.min.js (custom jQuery UI build with widget factory)
// – templates/
// – asset.html
// – ao.myWidget.js
// Then we can construct the widget as follows:
// ao.myWidget.js file:
define( “ao.myWidget”, [“jquery”, “text!templates/asset.html”, “underscore”, “jquery-ui.custom.min”], function ( $, assetHtml, _ ) {
    // define our widget under a namespace of our choice
    // “ao” is used here as a demonstration
    $.widget( “ao.myWidget”, {
        // Options to be used as defaults
        options: {},
        // Set up widget (e.g. create element, apply theming,
        // bind events, etc.)
        _create: function () {
            // _create will automatically run the first time
            // this widget is called. Put the initial widget
            // set-up code here, then we can access the element
            // on which the widget was called via this.element.
            // The options defined above can be accessed via
            // this.options
            // this.element.addStuff();
            // this.element.addStuff();
            // We can then use Underscore templating with
            // with the assetHtml that has been pulled in
            // var template = _.template( assetHtml );
            // this.content.append( template({}) );
        },
        // Destroy an instantiated plugin and clean up modifications
        // that the widget has made to the DOM
        destroy: function () {
            // this.element.removeStuff();
            // For UI 1.8, destroy must be invoked from the base
            // widget
            $.Widget.prototype.destroy.call( this );
            // For UI 1.9, define _destroy instead and don’t worry
            // about calling the base widget
        },
        methodB: function ( event ) {
            // _trigger dispatches callbacks the plugin user can
            // subscribe to
            // signature: _trigger( “callbackName”, [eventObject],
            // [uiObject] )
            this._trigger( “methodA”, event, {
                key: value
            });
        },
        methodA: function ( event ) {
            this._trigger(“dataChanged”, event, {
                key: value
            });
        },
        // Respond to any changes the user makes to the option method
        _setOption: function ( key, value ) {
            switch (key) {
            case “someValue”:
                // this.options.someValue = doSomethingWith( value );
                break;
            default:
                // this.options[ key ] = value;
                break;
            }
            // For UI 1.8, _setOption must be manually invoked from
            // the base widget
            $.Widget.prototype._setOption.apply( this, arguments );
            // For UI 1.9 the _super method can be used instead
            // this._super( “_setOption”, key, value );
        }
    });
});

Usage:

index.html:

1
<script data-main=”scripts/main” src=”http://requirejs.org/docs/release/1.0.1/minified/require.js“></script>

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
require({
    paths: {
        “boilerplate”: “../patterns/jquery.widget-factory.requirejs.boilerplate”
    }
}, [“require”, “jquery”, “jqueryui”, “boilerplate”],
function (req, $) {
    $(function () {
        var instance = $(“#elem”).myWidget();
        instance.myWidget(“methodB”);
    });
});

Further Reading

 

 

Globally And Per-Call Overridable Options (Best Options Pattern)

For our next pattern, we’ll look at an optimal approach to configuring options and defaults for a plugin. The way most of us are probably familiar with defining plugin options is to pass through an object literal of defaults to $.extend(), as demonstrated in our basic plugin boilerplate.

If, however, we’re working with a plugin with many customizable options that we would like users to be able to override either globally or on a per-call level, then we can structure things a little more optimally.

Instead, by referring to an options object defined within the plugin namespace explicitly (for example, $fn.pluginName.options) and merging this with any options passed through to the plugin when it is initially invoked, users have the option of either passing options through during plugin initialization or overriding options outside of the plugin (as demonstrated here).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*!
 * jQuery “best options” plugin boilerplate
 * Author: @cowboy
 * Further changes: @addyosmani
 * Licensed under the MIT license
 */
;(function ( $, window, document, undefined ) {
    $.fn.pluginName = function ( options ) {
        // Here’s a best practice for overriding “defaults”
        // with specified options. Note how, rather than a
        // regular defaults object being passed as the second
        // parameter, we instead refer to $.fn.pluginName.options
        // explicitly, merging it with the options passed directly
        // to the plugin. This allows us to override options both
        // globally and on a per-call level.
        options = $.extend( {}, $.fn.pluginName.options, options );
        return this.each(function () {
            var elem = $(this);
        });
    };
    // Globally overriding options
    // Here are our publicly accessible default plugin options
    // that are available in case the user doesn’t pass in all
    // of the values expected. The user is given a default
    // experience but can also override the values as necessary.
    // e.g. $fn.pluginName.key =”otherval”;
    $.fn.pluginName.options = {
        key: “value”,
        myMethod: function ( elem, param ) {
        }
    };
})( jQuery, window, document );

Usage:

1
2
3
$(“#elem”).pluginName({
  key: “foobar”
});

Further Reading

 

 

A Highly Configurable And Mutable Plugin Pattern

In this pattern, similar to Alex Sexton’s prototypal inheritance plugin pattern, logic for our plugin isn’t nested in a jQuery plugin itself. We instead define our plugin logic using a constructor and an object literal defined on its prototype. jQuery is then used for the actual instantiation of the plugin object.

Customization is taken to the next level by employing two little tricks, one of which we’ve seen in previous patterns:

  • Options can be overridden both globally and per collection of elements/
  • Options can be customized on a per-element level through HTML5 data attributes (as shown below). This facilitates plugin behavior that can be applied to a collection of elements but then customized inline without the need to instantiate each element with a different default value.

We don’t see the latter option in the wild too often, but it can be a significantly cleaner solution (as long as we don’t mind the inline approach). If wondering where this could be useful, imagine writing a draggable plugin for a large set of elements. We could go about customizing their options as follows:

1
2
3
4
$( “.item-a” ).draggable( {“defaultPosition”:”top-left”} );
$( “.item-b” ).draggable( {“defaultPosition”:”bottom-right”} );
$( “.item-c” ).draggable( {“defaultPosition”:”bottom-left”} );
//etc

But using our patterns inline approach, the following would be possible:

1
$( “.items” ).draggable();
1
2
3
html
<li class=”item” data-plugin-options=”{“defaultPosition”:”top-left”}”></div>
<li class=”item” data-plugin-options=”{“defaultPosition”:”bottom-left”}”></div>

And so on. We may well have a preference for one of these approaches, but it is just another variation worth being aware of.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/*
 * “Highly configurable” mutable plugin boilerplate
 * Author: @markdalgleish
 * Further changes, comments: @addyosmani
 * Licensed under the MIT license
 */
// Note that with this pattern, as per Alex Sexton’s, the plugin logic
// hasn’t been nested in a jQuery plugin. Instead, we just use
// jQuery for its instantiation.
;(function( $, window, document, undefined ){
  // our plugin constructor
  var Plugin = function( elem, options ){
      this.elem = elem;
      this.$elem = $(elem);
      this.options = options;
      // This next line takes advantage of HTML5 data attributes
      // to support customization of the plugin on a per-element
      // basis. For example,
      // <div class=”item” data-plugin-options=”{‘message’:’Goodbye World!’}”></div>
      this.metadata = this.$elem.data( “plugin-options” );
    };
  // the plugin prototype
  Plugin.prototype = {
    defaults: {
      message: “Hello world!”
    },
    init: function() {
      // Introduce defaults that can be extended either
      // globally or using an object literal.
      this.config = $.extend( {}, this.defaults, this.options,
      this.metadata );
      // Sample usage:
      // Set the message per instance:
      // $( “#elem” ).plugin( { message: “Goodbye World!”} );
      // or
      // var p = new Plugin( document.getElementById( “elem” ),
      // { message: “Goodbye World!”}).init()
      // or, set the global default message:
      // Plugin.defaults.message = “Goodbye World!”
      this.sampleMethod();
      return this;
    },
    sampleMethod: function() {
      // e.g. show the currently configured message
      // console.log(this.config.message);
    }
  }
  Plugin.defaults = Plugin.prototype.defaults;
  $.fn.plugin = function( options ) {
    return this.each(function() {
      new Plugin( this, options ).init();
    });
  };
  // optional: window.Plugin = Plugin;
})( jQuery, window, document );

Usage:

1
2
3
$(“#elem”).plugin({
  message: “foobar”
});

Further Reading

 

What Makes A Good Plugin Beyond Patterns?

At the end of the day, design patterns are just one facet to writing maintainable jQuery plugins. There are a number of other factors worth considering and I would like to share my own criteria for selecting third-party plugins to address some of the other concerns. I hope this helps increase the overall quality of your plugin projects:

Quality

Adhere to best practices with respect to both the JavaScript and jQuery that you write. Are efforts being made to lint the plugin code using either jsHint or jsLint? Is the plugin written optimally?

Code Style

Does the plugin follow a consistent code style guide such as the jQuery Core Style Guidelines? If not, is your code at least relatively clean and readable?

Compatibility

Which versions of jQuery is the plugin compatible with? Has it been tested with the latest jQuery-git builds or latest stable? If the plugin was written before jQuery 1.6, then it might have issues with attributes and properties, because the way they were approached changed in that release.

New versions of jQuery offer improvements and opportunities for the jQuery project to improve on what the core library offers. With this comes occasional breakages (mainly in major releases) as we move towards a better way of doing things. I’d like to see plugin authors update their code when necessary or, at a minimum, test their plugins with new versions to make sure everything works as expected.

Reliability

The plugin should come with its own set of unit tests. Not only do these prove it actually functions as expected, but they can also improve the design without breaking it for end users. I consider unit tests essential for any serious jQuery plugin that is meant for a production environment, and they’re not that hard to write. For an excellent guide to automated JavaScript testing with QUnit, you may be interested in “Automating JavaScript Testing With QUnit,” by Jörn Zaefferer.

Performance

If the plugin needs to perform tasks that require extensive processing or heavily manipulation of the DOM, one should follow best practices for benchmarking to help minimize this. Use jsPerf.com to test segments of the code to a) how well it performs in different browsers and b) discover what, if anything, might be optimized further.

Documentation

If the intension is for other developers to use the plugin, ensure that it’s well documented. Document the API and how the plugin is to be used. What methods and options does the plugin support? Does it have any gotchas that users need to be aware of? If users cannot figure out how to use the plugin, they’ll likely look for an alternative. It is also of great help to comment your plugin code. This is by far the best gift you can offer other developers. If someone feels they can navigate your code base well enough to use it or improve it, then you’ve done a good job.

Likelihood of maintenance

When releasing a plugin, estimate how much time may be required for maintenance and support. We all love to share our plugins with the community, but one needs to set expectations for ones ability to answer questions, address issues and make continuous improvements. This can be done simply by stating the project intentions for maintenance support upfront in the README file.

Conclusions

In this chapter, we explored several time-saving design patterns and best practices that can be employed to improve how jQuery plugins can be written. Some are better suited to certain use cases than others, but I hope on the whole these patterns are useful.

Remember, when selecting a pattern, it is important to be practical. Don’t use a plugin pattern just for the sake of it, rather, invest time in understanding the underlying structure, and establish how well it solves your problem or fits the component you’re trying to build.