[Edit] 7 months later
Quote from github project
jQuery is not good, and jQuery plugins are not like modular code.
Seriously, jQuery plugins are not sound architecture. Writing jQuery-heavy code is also stupid.
[Original]
Since I have criticized this template, I will offer an alternative.
To make life easier, it depends on jQuery 1.6+ and ES5 (use ES5 Shim ).
I spent some time redesigning the plugin template that you provided and came up with my own.
References:
Comparison:
I reorganized the template so that it is divided into a template (85%) and a forest code (15%). The goal is that you only need to edit the forest code, and you can leave the template template unchanged. For this I used
- inheritance
var self = Object.create(Base) Instead of editing the Internal class that you have, you should edit the subclass. All of your default templates / functions should be in a base class (called Base in my code). - convention
self[PLUGIN_NAME] = main; By convention, the plugin defined in jQuery defines the define method on self[PLUGIN_NAME] by default. This is considered the module of the main plugin and for clarity has a separate external method. - Monkey patch
$.fn.bind = function _bind ... Using patches for monkeys means that the event namespace runs automatically for you under the hood. This functionality is free and requires no reading costs ( getEventNS is getEventNS all the time).
OO Methods
Itโs better to stick with proper JavaScript OO rather than classic OO emulation. You must use Object.create . (ES5 just uses padding to update older browsers).
var Base = (function _Base() { var self = Object.create({}); return self; })(); var Wrap = (function _Wrap() { var self = Object.create(Base); return self; })(); var w = Object.create(Wrap);
This is different from the standard new and .prototype OO-based people. This approach is preferable because it re-reveals the concept that JavaScript has only objects and this is a prototype OO approach.
[ getEventNS ]
As mentioned, this method has been reorganized by overriding .bind and .unbind to automatically enter namespaces. These methods are overwritten in the private version of jQuery $.sub() . The rewritten methods behave the same as the namespace does. It namespaces events are uniquely based on the plugin and the instance of the plugin shell around the HTMLElement (using .ns .
[ getData ]
This method has been replaced with a .data method, which has the same API as jQuery.fn.data . The fact that it has the same API makes it easy to use is basically a thin shell around jQuery.fn.data with a namespace. This allows you to set key / value pairs that are immediately saved only for this plugin. Several plugins can use this method in parallel without conflict.
[ publicMethods ]
The publicMethods object is replaced in any way that is defined on Wrap by automatically public. You can call any method on a wrapped object directly, but in fact you do not have access to the wrapped object.
[ $.fn[PLUGIN_NAME] ]
This has been reorganized, so it provides a more standardized API. This is api
$(selector).PLUGIN_NAME("methodName", {/* object hash */}); // OR $(selector).PLUGIN_NAME({/* object hash */}); // methodName defaults to PLUGIN_NAME
elements in the selector are automatically wrapped in a Wrap object, the method is called or each selected element from the selector, and the return value is always equal to $.Deferred .
This standardizes the API and return type. You can then call .then on the returned deferred to get the actual data you care about. Using pending here is very powerful for abstracting from whether the plugin is synchronous or asynchronous.
_create
Added function to create caching. This is called to turn an HTMLElement into a wrapped element, and each HTMLE element will only be wrapped once. This caching gives you a significant reduction in memory.
$.PLUGIN_NAME
Added another public method for the plugin (just two!).
$.PLUGIN_NAME(elem, "methodName", {}); $.PLUGIN_NAME([elem, elem2, ...], "methodName", {}); $.PLUGIN_NAME("methodName", { elem: elem, cb: function() { } });
All parameters are optional. elem by default <body> , "methodName" by default is "PLUGIN_NAME" and {/* options */} by default {} .
This API is very flexible (with 14 method overloads!) And standard enough to get used to the syntax for each method that your plugin will expose.
Public exposition
Wrap , create and $ objects are displayed globally. This will allow users of the advanced plugin to make the most of your plugin. They can use create and modified subbed $ in their development, as well as the monkey patch Wrap . This allows you, for example, to connect to your plugin methods. All three of them are marked with a _ in front of their name, so they are internal and their use violates the guarantee that your plugin works.
The internal defaults object also appears as $.PLUGIN_NAME.global . This allows users to override your default values โโand install the global defaults plugin. In this plugin setting, all hashes are passed to methods, since objects are combined with default values, so this allows users to set global default values โโfor all your methods.
Actual code
(function($, jQuery, window, document, undefined) { var PLUGIN_NAME = "Identity"; // default options hash. var defaults = { // TODO: Add defaults }; // ------------------------------- // -------- BOILERPLATE ---------- // ------------------------------- var toString = Object.prototype.toString, // uid for elements uuid = 0, Wrap, Base, create, main; (function _boilerplate() { // over-ride bind so it uses a namespace by default // namespace is PLUGIN_NAME_<uid> $.fn.bind = function _bind(type, data, fn, nsKey) { if (typeof type === "object") { for (var key in type) { nsKey = key + this.data(PLUGIN_NAME)._ns; this.bind(nsKey, data, type[key], fn); } return this; } nsKey = type + this.data(PLUGIN_NAME)._ns; return jQuery.fn.bind.call(this, nsKey, data, fn); }; // override unbind so it uses a namespace by default. // add new override. .unbind() with 0 arguments unbinds all methods // for that element for this plugin. ie calls .unbind(_ns) $.fn.unbind = function _unbind(type, fn, nsKey) { // Handle object literals if ( typeof type === "object" && !type.preventDefault ) { for ( var key in type ) { nsKey = key + this.data(PLUGIN_NAME)._ns; this.unbind(nsKey, type[key]); } } else if (arguments.length === 0) { return jQuery.fn.unbind.call(this, this.data(PLUGIN_NAME)._ns); } else { nsKey = type + this.data(PLUGIN_NAME)._ns; return jQuery.fn.unbind.call(this, nsKey, fn); } return this; }; // Creates a new Wrapped element. This is cached. One wrapped element // per HTMLElement. Uses data-PLUGIN_NAME-cache as key and // creates one if not exists. create = (function _cache_create() { function _factory(elem) { return Object.create(Wrap, { "elem": {value: elem}, "$elem": {value: $(elem)}, "uid": {value: ++uuid} }); } var uid = 0; var cache = {}; return function _cache(elem) { var key = ""; for (var k in cache) { if (cache[k].elem == elem) { key = k; break; } } if (key === "") { cache[PLUGIN_NAME + "_" + ++uid] = _factory(elem); key = PLUGIN_NAME + "_" + uid; } return cache[key]._init(); }; }()); // Base object which every Wrap inherits from Base = (function _Base() { var self = Object.create({}); // destroy method. unbinds, removes data self.destroy = function _destroy() { if (this._alive) { this.$elem.unbind(); this.$elem.removeData(PLUGIN_NAME); this._alive = false; } }; // initializes the namespace and stores it on the elem. self._init = function _init() { if (!this._alive) { this._ns = "." + PLUGIN_NAME + "_" + this.uid; this.data("_ns", this._ns); this._alive = true; } return this; }; // returns data thats stored on the elem under the plugin. self.data = function _data(name, value) { var $elem = this.$elem, data; if (name === undefined) { return $elem.data(PLUGIN_NAME); } else if (typeof name === "object") { data = $elem.data(PLUGIN_NAME) || {}; for (var k in name) { data[k] = name[k]; } $elem.data(PLUGIN_NAME, data); } else if (arguments.length === 1) { return ($elem.data(PLUGIN_NAME) || {})[name]; } else { data = $elem.data(PLUGIN_NAME) || {}; data[name] = value; $elem.data(PLUGIN_NAME, data); } }; return self; })(); // Call methods directly. $.PLUGIN_NAME(elem, "method", option_hash) var methods = jQuery[PLUGIN_NAME] = function _methods(elem, op, hash) { if (typeof elem === "string") { hash = op || {}; op = elem; elem = hash.elem; } else if ((elem && elem.nodeType) || Array.isArray(elem)) { if (typeof op !== "string") { hash = op; op = null; } } else { hash = elem || {}; elem = hash.elem; } hash = hash || {} op = op || PLUGIN_NAME; elem = elem || document.body; if (Array.isArray(elem)) { var defs = elem.map(function(val) { return create(val)[op](hash); }); } else { var defs = [create(elem)[op](hash)]; } return $.when.apply($, defs).then(hash.cb); }; // expose publicly. Object.defineProperties(methods, { "_Wrap": { "get": function() { return Wrap; }, "set": function(v) { Wrap = v; } }, "_create":{ value: create }, "_$": { value: $ }, "global": { "get": function() { return defaults; }, "set": function(v) { defaults = v; } } }); // main plugin. $(selector).PLUGIN_NAME("method", option_hash) jQuery.fn[PLUGIN_NAME] = function _main(op, hash) { if (typeof op === "object" || !op) { hash = op; op = null; } op = op || PLUGIN_NAME; hash = hash || {}; // map the elements to deferreds. var defs = this.map(function _map() { return create(this)[op](hash); }).toArray(); // call the cb when were done and return the deffered. return $.when.apply($, defs).then(hash.cb); }; }()); // ------------------------------- // --------- YOUR CODE ----------- // ------------------------------- main = function _main(options) { this.options = options = $.extend(true, defaults, options); var def = $.Deferred(); // Identity returns this & the $elem. // TODO: Replace with custom logic def.resolve([this, this.elem]); return def; } Wrap = (function() { var self = Object.create(Base); var $destroy = self.destroy; self.destroy = function _destroy() { delete this.options; // custom destruction logic // remove elements and other events / data not stored on .$elem $destroy.apply(this, arguments); }; // set the main PLUGIN_NAME method to be main. self[PLUGIN_NAME] = main; // TODO: Add custom logic for public methods return self; }()); })(jQuery.sub(), jQuery, this, document);
As you can see, the code you need to change is below the YOUR CODE . The Wrap object acts similar to your Internal object.
The main function is the main function called with $.PLUGIN_NAME() or $(selector).PLUGIN_NAME() and should contain the main logic.