I’ve been doing quite a bit of work in JavaScript recently. It’s a major shift from regular server-side grind. I learned a few patterns and became more aware of different architectural practices when writing front-end code. I am starting to slowly get over my love/hate relationship with all things client-side. I absolutely love the responsiveness and interactivity of a modern user interface.
Yet, I despise writing JavaScript code. To me it’s the mind-boggling dynamic nature of JavaScript that makes it so complicated. So, what I’d like to do is start quick series about different JavaScript tips and tricks I learned over time, which essentially make writing JavaScript a slightly better experience (at least in my opinion).
Like many others, I write all of my JavaScript in jQuery. I’ve already talked about how to structure jQuery code, today I’d like to discuss a cool plugin pattern i picked up from Twitter Bootstrap while using it on one of my projects. Here is the skeleton.
(function () {
/*
Plugin class definition
*/
var Plugin,
privateMethod;
Plugin = (function () {
/*
Plugin constructor
*/
function Plugin(element, options) {
this.settings = $.extend({}, $.fn.plugin.defaults, options);
this.$element = $(element);
/* Do some initialization
*/
}
/*
Public method
*/
Plugin.prototype.doSomething = function () {
/* Method body here
*/
};
return Plugin;
})();
/*
Private method
*/
privateMethod = function () {
/* Method body here
*/
};
/*
Plugin definition
*/
$.fn.plugin = function (options) {
var instance;
instance = this.data('plugin');
if (!instance) {
return this.each(function () {
return $(this).data('plugin', new Plugin(this, options));
});
}
if (options === true) return instance;
if ($.type(options) === 'string') instance[options]();
return this;
};
$.fn.plugin.defaults = {
property1: 'value',
property2: 'value'
};
/*
Apply plugin automatically to any element with data-plugin
*/
$(function () {
return new Plugin($('[data-plugin]'));
});
}).call(this);
Calling the above plugin works like so:
$('selector').plugin();
Starting at the top we see that all of our code is inside a self-executing anonymous function. This is a pretty standard pattern in JavaScript used to isolate code into “blocks.” Next, we get to the interesting part, which sets this aside from other patterns. All of the plugin logic resides in the Plugin object. This allows you to use prototypal inheritance of JavaScript to extend plugins when necessary.
Things get a little tricky in our jQuery plugin definition. What we’re attempting to do here is store the Plugin object inside the data attribute of each element resolved through the selector. So here is what’s happening inside:
- If the selector in calling code resolves to single element we’ll try to pull the instance of the Plugin object from that element’s data attribute and return it.
- If nothing is found, assume we’re working with a collection of elements and try to iterate through it while configuring (using constructor) and storing the instance of Plugin object in the data attribute.
- If the calling code passes ‘true’ to the plugin, we’ll attempt to return current instance of the Plugin object. This will only work with selectors resolving to single element.
- If the calling code passes a string to the plugin, we’ll assume it’s the name of a method of our plugin class and attempt to execute it.
Last but not least, we’ll try to automatically apply the plugin to any element marked with its specific data attribute.
If you’re using CoffeeScript, here is the code that generates the above skeleton.
###
Plugin class definition
###
class Plugin
###
Plugin constructor
###
constructor: (element, options) ->
this.settings = $.extend({}, $.fn.plugin.defaults, options)
this.$element = $(element)
### Do some initialization ###
###
Public method
###
doSomething: () ->
### Method body here ###
###
Private method
###
privateMethod = () ->
### Method body here ###
###
Plugin definition
###
$.fn.plugin = (options) ->
instance = this.data('plugin')
if not instance
return this.each ->
$(this).data('plugin', new Plugin this, options)
return instance if options is true
instance[options]() if $.type(options) is 'string'
return this
$.fn.plugin.defaults =
property1: 'value'
property2: 'value'
###
Apply plugin automatically to any element with data-plugin
###
$ -> new Plugin($('[data-plugin]'))
If you have questions or improvements, share them in comments. Thanks for reading!