La Grange web + création
view ourwork
Javascript jQuery

A pattern for abstract jQuery plugins

by Martin Vézina | 2012-12-17

I already posted about prototypal inheritance in Javascript, and I will demonstrate how I use it in the context of a jQuery plugin.

To make an animated button plugin, I needed to have a way to create an abstract jQuery plugin, in the sense that some of the functionnalities could not be generic and were left to be defined by the user of the plugin. The function that defines the animation cannot be defined in the plugin itself, because it depends entirely on the concrete buttons that are to be animated.

I have read many tutorials and blogs about jQuery plugin authoring, some of which you can find in the resources at the end of this article, but none seemed right for what I wanted to do. I wanted to keep things simple, so I mixed some of the concepts that originate from different parts of the web and were conveniently gathered by Addy Osmani.

Let's start with an implementation of the plugin if we were to use Alex Sexton's and Scott Gonzalez's plugin pattern almost directly, which is a really good start.

I modified the original version, mostly to be able to access methods of the plugin with a call to the plugin instead of by retrieving the object instance for each element. It is still possible to access the instance through each element's data, but you also have the option to call a method on all instances in a single command instead of having to loop through instances each time you wanted to act upon them. It is particularly useful in my case where I might want to activate/deactivate a bunch of buttons at the same time.

Look at the way the animation is initialized. The function that defines the animation (setAnimation) is custom and has to be. It depends on the markup of the element, as well as on the intention of the motion designer. It cannot be defined in the plugin, because if it were, we would need one plugin for each type of button, and it would mean a lot of code duplication.

For this first version, we passed the setAnimation function inside the options object when we instantiate the plugin. It's probably not the best idea to leave the method in the options object, as it's not really an option, but we'll leave it at that for now.

Still, we can reuse the plugin code even for different types of buttons, because each button gets its own custom function.

$('.thumbnail').AbstractButton(
  {
    setAnimation: function(){
      //animation setup
    },
    autoActivate : false
  }
);

As you see, we need to define the function that sets the animation each time we apply the plugin on a jQuery set. Something's not quite right yet. We define how the plugin works at the same time as we use it, whereas these two things are completely different concepts.

Suppose that we had different jQuery sets that used the same button definition, so would share setAnimation. To avoid duplicating the function, we could put it in a variable and use it when we call the plugin:

var thumbnailSetup = function(){
	//animation setup
};
	
$('.smallThumbnail').AbstractButton({setAnimation:thumbnailSetup, autoActivate : false});
$('.bigThumbnail').AbstractButton({setAnimation:thumbnailSetup, autoActivate : true});

The problem is that the plugin does not force us to cache the function like we did. We could have duplicated the function instead of putting it in a variable, nobody would've complained. It's better to have only one instance of the function, but if both thumbnails were instanciated in different parts of code, both would need to access thumbnailSetup, so we'd need to devise a way to share that variable, probably through a namespace.

Also worth noting is that these two instances of the plugin are identical, but there is not much in the code that indicates it. If the setAnimation option was different in both cases, we'd have two different types of buttons. There could be many uses of AbstractButton elsewehere in the application with completely different animations, and they would still be using the same generic AbstractButton plugin. The way the plugin is applied, there is no explicit differenciation between distinct types of buttons.

A plugin factory

Ideally, we would have a way to:

  • Make it clear that some properties of the plugin are left undefined but are not optionnal
  • Separate the process of defining the plugin from the process of applying it
  • Share some user-defined properties among different instances of a plugin
  • Make it clear when different instances of the plugin use different definitions

An abstract definition

From the pattern that we used, we already have an object representing the plugin functionnalities, that is instantiated each time we apply the plugin to an element. So our call to the plugin's function, in fact, just creates an object of this type.

Instead of adding the specific setAnimation to each instance of that object, we will create a second prototype level containing the specific code for each type of button and instantiate that object instead.

We'll begin by writing our basic object without taking setAnimation in its options.

var PluginPrototype = {
  init : function(name, el, options) {
    this.name = name;
    this.el = el;
    this.$el = $(el);
    this.options = $.extend({}, defaults, options);
    this.activate();
    if(this.options.autoActivate === false) {
      this.deactivate();
    }

  },

  //...
};

A concrete implementation

For each type of button, we will chain a new object that will contain only the specifics for that type, namely setAnimation.

var PluginPrototype = {
  //...
};

var ThumbnailButton = Object.create(PluginPrototype);

ThumbnailButton.setAnimation = function(){
  //animation setup
};

The process can be moved in a simple method:

var PluginPrototype = {
  //...
};
	
function extendPlugin(setAnimation) {
  var ConcretePlugin = Object.create(PluginPrototype);
  ConcretePlugin.setAnimation = setAnimation;
  return ConcretePlugin;
}	

The Factory

Now that we have a convenient way to create concrete objects for different implementations, we need a way to use them in the context of a jQuery plugin. Each newly created object type should be attached to its own jQuery plugin.

If we can write a method that creates a type of object for use in a plugin, we could as well have the method define the plugin at the same time... we could write a jQuery plugin factory.

The plugin factory is loosely based on the techniques used for namespacing plugins. In my case I dont use it because I want to namespace my plugin, but because the plugin factory method is convenient to create different plugins with the same base.

The plugin factory method could be in any namespace we wish to use, but for convenience, lets use jQuery namespace as it is (obviously) available to any code that uses jQuery. The function name is not the one that will be used for the concrete plugins, but should be descriptive. For my abstract button, I chose, well, AbstractButton as the name for the plugin factory : $.AbstractButton = function() {...}

First we'll move the plugin definition inside that function. It has a name parameter to create a plugin under that name. Note that any generic string could be prepended or appended to concreteName to namespace the plugin.

var pluginName = 'AbstractButton';
var pluginNamespace = 'Button';
$[pluginName] = function (concreteName) {
  var completeName = pluginNamespace + '_' + concreteName;
  $.fn[completeName] = function(options) {
    //...
  };
};

then we'll merge our object creation code in that function:

var PluginPrototype = {
  //...
};	

var pluginName = 'AbstractButton';
var pluginNamespace = 'Button';

$[pluginName] = function (concreteName, setAnimation) {
  var ConcretePlugin = Object.create(PluginPrototype);
  ConcretePlugin.name = concreteName;
  ConcretePlugin.setAnimation = setAnimation;
  
  var completeName = pluginNamespace + '_' + concreteName;
  $.fn[completeName] = function(options) {
    //...
  };
};

In the plugin itself, the only difference with the previous version is that we create an instance of the concrete plugin object instead of the generic one:

var instance = Object.create(ConcretePlugin);

Final code

For the final code, I included a (simplified) shim for older browsers, because Object.create is part of EcmaScript 5.

Concrete example

Here is the complete version of the code for the above example:

Pattern

Here is the simplified pattern:

Conclusion

The plugin factory could be more generic, and be extracted in a seperate file to be reused by other plugins, but I chose to leave it in the plugin's core file because then it has no dependencies other than its "true" dependencies. It does not rely on external helper code merely to create itself. Still, it could be extracted in order to stay DRY.

The techniques exposed don't need to be used as is. For example, prototype inheritance is not the only way to extend objects, you could use mixins if it's more suitable in your case. Still, I hope that this pattern will help you in devising your own way of developping jQuery plugins.

Further reading and resources

  • The perfect jQuery plugin : http://bitovi.com/blog/2010/10/writing-the-perfect-jquery-plugin.html
  • Addy Osmani's Essential jQuery plugin patterns : http://coding.smashingmagazine.com/2011/10/11/essential-jquery-plugin-patterns/
  • Alex Sextons' jQuery plugin pattern : http://alexsexton.com/blog/2010/02/using-inheritance-patterns-to-organize-large-jquery-applications/
  • EcmaScript 5 shim : https://github.com/kriskowal/es5-shim

Comments

Choose acategory
Javascript  [ 5 ]
jQuery  [ 3 ]
Animation  [ 3 ]
LESS CSS  [ 2 ]
responsive  [ 2 ]
html5  [ 2 ]
CSS  [ 2 ]
PHP  [ 1 ]
Inheritance  [ 1 ]
Easel.js  [ 1 ]
Basics of programming  [ 1 ]
Prototypes  [ 1 ]
Canvas  [ 1 ]
ImageMagick  [ 1 ]
Vanilla Javascript  [ 1 ]
GreenSock  [ 1 ]
Call us

La Grange
32 St-Charles Ouest Suite 340
Longueuil (QC) J4H 1C6

Contact
514.750.4944
info@la-grange.ca

Twitter