I’ve started to write few jQuery plugins and figured it’d be nice to setup my IDE with a jQuery plugin template.
I have been reading some articles and posts on this site related to plugin convention, design, etc.. and thought I’d try and consolidate all of that.
Below is my template, I am looking to use it frequently so was keen to ensure it generally conforms to jQuery plugin design convention and whether the idea of having multiple internal methods (or even its general design) would impact performance and be prone to memory issues.
(function($)
{
var PLUGIN_NAME = "myPlugin"; // TODO: Plugin name goes here.
var DEFAULT_OPTIONS =
{
// TODO: Default options for plugin.
};
var pluginInstanceIdCount = 0;
var I = function(/*HTMLElement*/ element)
{
return new Internal(element);
};
var Internal = function(/*HTMLElement*/ element)
{
this.$elem = $(element);
this.elem = element;
this.data = this.getData();
// Shorthand accessors to data entries:
this.id = this.data.id;
this.options = this.data.options;
};
/**
* Initialises the plugin.
*/
Internal.prototype.init = function(/*Object*/ customOptions)
{
var data = this.getData();
if (!data.initialised)
{
data.initialised = true;
data.options = $.extend(DEFAULT_OPTIONS, customOptions);
// TODO: Set default data plugin variables.
// TODO: Call custom internal methods to intialise your plugin.
}
};
/**
* Returns the data for relevant for this plugin
* while also setting the ID for this plugin instance
* if this is a new instance.
*/
Internal.prototype.getData = function()
{
if (!this.$elem.data(PLUGIN_NAME))
{
this.$elem.data(PLUGIN_NAME, {
id : pluginInstanceIdCount++,
initialised : false
});
}
return this.$elem.data(PLUGIN_NAME);
};
// TODO: Add additional internal methods here, e.g. Internal.prototype.<myPrivMethod> = function(){...}
/**
* Returns the event namespace for this widget.
* The returned namespace is unique for this widget
* since it could bind listeners to other elements
* on the page or the window.
*/
Internal.prototype.getEventNs = function(/*boolean*/ includeDot)
{
return (includeDot !== false ? "." : "") + PLUGIN_NAME + "_" + this.id;
};
/**
* Removes all event listeners, data and
* HTML elements automatically created.
*/
Internal.prototype.destroy = function()
{
this.$elem.unbind(this.getEventNs());
this.$elem.removeData(PLUGIN_NAME);
// TODO: Unbind listeners attached to other elements of the page and window.
};
var publicMethods =
{
init : function(/*Object*/ customOptions)
{
return this.each(function()
{
I(this).init(customOptions);
});
},
destroy : function()
{
return this.each(function()
{
I(this).destroy();
});
}
// TODO: Add additional public methods here.
};
$.fn[PLUGIN_NAME] = function(/*String|Object*/ methodOrOptions)
{
if (!methodOrOptions || typeof methodOrOptions == "object")
{
return publicMethods.init.call(this, methodOrOptions);
}
else if (publicMethods[methodOrOptions])
{
var args = Array.prototype.slice.call(arguments, 1);
return publicMethods[methodOrOptions].apply(this, args);
}
else
{
$.error("Method '" + methodOrOptions + "' doesn't exist for " + PLUGIN_NAME + " plugin");
}
};
})(jQuery);
Thanks in advance.
[Edit] 7 months later
Quoting from the github project
Seriously “jQuery plugins” are not a sound architecture strategy. Writing code with a hard dependency on jQuery is also silly.
[Original]
Since I gave critique about this template I will propose an alternative.
To make live easier this relies on
jQuery1.6+ and ES5 (use the ES5 Shim).I’ve spend some time re-designing the plugin template you’ve given and rolled out my own.
Links:
Comparison:
I’ve refactored the template so that it’s split into boilerplate (85%) and scaffolding code (15%). The intention is that you only have to edit the scaffolding code and you can keep leave boilerplate code untouched. To achieve this I’ve used
var self = Object.create(Base)Rather then editing theInternalclass you have directly you should be editing a sub class. All your template / default functionality should be in a base class (calledBasein my code).self[PLUGIN_NAME] = main;By convention the plugin defined on jQuery will call the method define onself[PLUGIN_NAME]by default. This is considered themainplugin method and has a seperate external method for clarity.$.fn.bind = function _bind ...Use of monkey patching means that the event namespacing is done automatically for you under the hood. This functionality is free and does not come at the cost of readability (callinggetEventNSall the time).OO Techniques
It’s better to stick to proper JavaScript OO rather then classical OO emulation. To achieve this you should use
Object.create. (which ES5 just use the shim to upgrade old browsers).This is different from the standard
newand.prototypebased OO people are used to. This approach is preferred because it re-inforces the concept that there are only Objects in JavaScript and it’s a prototypical OO approach.[
getEventNs]As mentioned this method has been refactored away by overriding
.bindand.unbindto automatically inject namespaces. These methods are overwritten on the private version of jQuery$.sub(). The overwritten methods behave the same way as your namespacing does. It namespaces events uniquely based on plugin and instance of a plugin wrapper around a HTMLElement (Using.ns.[
getData]This method has been replaced with a
.datamethod that has the same API asjQuery.fn.data. The fact that it’s the same API makes it easier to use, its basically a thin wrapper aroundjQuery.fn.datawith namespacing. This allows you to set key/value pair data that is immediatley stored for that plugin only. Multiple plugins can use this method in parallel without any conflicts.[
publicMethods]The publicMethods object has been replaced by any method being defined on
Wrapbeing automatically public. You can call any method on a Wrapped object directly but you do not actually have access to the wrapped object.[
$.fn[PLUGIN_NAME]]This has been refactored so it exposes a more standardized API. This api is
the elements in the selector are automatically wrapped in the
Wrapobject, the method is called or each selected element from the selector and the return value is always a$.Deferredelement.This standardizes the API and the return type. You can then call
.thenon the returned deferred to get out the actual data you care about. The use of deferred here is very powerful for abstraction away whether the plugin is synchronous or asynchronous._createA caching create function has been added. This is called to turn a
HTMLElementinto a Wrapped element and each HTMLElement will only be wrapped once. This caching gives you a solid reduction in memory.$.PLUGIN_NAMEAdded another public method for the plugin (A total of two!).
All parameters are optional.
elemdefaults to<body>,"methodName"defaults to"PLUGIN_NAME"and{/* options */}defaults to{}.This API is very flexible (with 14 method overloads!) and standard enough to get used to the syntnax for every method your plugin will expose.
Public exposure
The
Wrap,createand$objects are exposed globally. This will allow advanced plugin users maximum flexibility with your plugin. They can usecreateand the modified subbed$in their development and they can also monkey patchWrap. This allows for i.e. hooking into your plugin methods. All three of these are marked with a_in front of their name so they are internal and using them breaks the garantuee that your plugin works.The internal
defaultsobject is also exposed as$.PLUGIN_NAME.global. This allows users to override your defaults and set plugin globaldefaults. In this plugin setup all hashes past into methods as objects are merged with the defaults, so this allows users to set global defaults for all your methods.Actual Code
As can be seen the code your supposed to edit is below the
YOUR CODEline. TheWrapobject acts similarly to yourInternalobject.The function
mainis the main function called with$.PLUGIN_NAME()or$(selector).PLUGIN_NAME()and should contain your main logic.