A Highly Configurable and Mutable Plug-in Pattern

In this pattern, similar to Alex Sexton’s prototypal inheritance plug-in pattern, logic for our plug-in isn’t nested in a jQuery plug-in itself. We instead define our plug-in logic using a constructor and an object literal defined on its prototype. jQuery is then used for the actual instantiation of the plug-in 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 plug-in 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 you’re wondering where this could be useful, imagine writing a draggable plug-in for a large set of elements. We could go about customizing their options as follows:

$( ".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:

$( ".items" ).draggable();
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.

/*
 * "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 plug-in logic
// hasn't been nested in a jQuery plug-in. Instead, we just use
// jQuery for its instantiation.

;(function( $, window, document, undefined ){

  // our plug-in 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 plug-in 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 plug-in 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:

$("#elem").plugin({
  message: "foobar"
});

Get Learning JavaScript Design Patterns now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.