JQuery plugin template - best practice, convention, performance and memory impact

I started writing some jQuery plugins and realized that it would be nice to customize my IDE using the jQuery plugin template.

I read some articles and posts on this site related to plugin convention, design, etc., and thought that I would try to combine all of this.

Below is my template, I am looking to use it often, so he was interested in making it generally consistent with the jQuery plugin design convention and whether the idea of โ€‹โ€‹having several internal methods (or even a general design) could affect performance and be prone to memory problems .

(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, eg 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.

+58
javascript jquery jquery-plugins
May 12, '11 at 15:10
source share
4 answers

[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", {/* options */}); $.PLUGIN_NAME([elem, elem2, ...], "methodName", {/* options */}); $.PLUGIN_NAME("methodName", { elem: elem, /* [elem, elem2, ...] */ cb: function() { /* success callback */ } /* further options */ }); 

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.

+26
Jun 07 2018-11-11T00:
source share

While back, I created a plugin generator based on a blog article I read: http://jsfiddle.net/KeesCBakker/QkPBF/ . This may be helpful. It is quite simple and direct. Any comments would be greatly appreciated.

You can branch out your own generator and change it to your needs.

Ps. This is the generated body:

 (function($){ //My description function MyPluginClassName(el, options) { //Defaults: this.defaults = { defaultStringSetting: 'Hello World', defaultIntSetting: 1 }; //Extending options: this.opts = $.extend({}, this.defaults, options); //Privates: this.$el = $(el); } // Separate functionality from object creation MyPluginClassName.prototype = { init: function() { var _this = this; }, //My method description myMethod: function() { var _this = this; } }; // The actual plugin $.fn.myPluginClassName = function(options) { if(this.length) { this.each(function() { var rev = new MyPluginClassName(this, options); rev.init(); $(this).data('myPluginClassName', rev); }); } }; })(jQuery); 
+28
Jun 09 2018-11-11T00:
source share

I resort to searching and land here, so I have to post some ideas: first I agree with @ Reynos.

The very code that the jQuery plugin is trying to build is actually ... not a plugin! This is just an object stored in memory that is referenced by the data property of the node / element. This is because jQuery should be considered and used as a tool next to the class library (to eliminate js inconsistencies from the OO architecture) to create better code and yes, this is not bad at all!

If you don't like the classic OO behavior, stick with a prototype library like clone .

So what are our options really?

  • use jQueryUI / Widget or similar library that hides technical and provides abstraction
  • not to use them because of difficulties, a learning curve, and God knows about future changes.
  • do not use them because you want to insist on a modular design, create small increases later
  • do not use them, because you may need to port / connect your code with various libraries.

Suppose the problems are addressed in the following scenario (see the complexity of this question: Which jQuery plugin design template should I use? ):

we have nodes A, B, and C that store the reference to the object in the data property

some of them store information in public and private public internal objects , some classes of these objects are related to inheritance , all these nodes also need private and public singlets to work best.

What will we do? See Figure:

 classes : | ABC ------------------case 1---------- members | | | | of | vvv an object | var a=new A, b=new B, c=new C at | B extends A node X : | a, b, c : private ------------------case 2--------- members | | | | of | vvv an object | var aa=new A, bb=new B, cc=new C at | BB extends AA node Y : | aa, bb, cc : public -------------------case 3-------- members | | | | of | vvv an object | var d= D.getInstance() (private), at | e= E.getInstance() (public) node Z : | D, E : Singletons 

as you can see, each node refers to an object - the jQuery approach - but these objects change a lot; they contain the properties of an object with various data stored in or even singleton, which should be ... single in memory, such as prototype functions of objects. We do not want every class A object function to be duplicated in memory in every node object!

Before my answer, see the general approach that I saw in jQuery plugins - some of them are very popular, but I don't say names:

 (function($, window, document, undefined){ var x = '...', y = '...', z = '...', container, $container, options; var myPlugin = (function(){ //<----the game is lost! var defaults = { }; function init(elem, options) { container = elem; $container = $(elem); options = $.extend({}, defaults, options); } return { pluginName: 'superPlugin', init: function(elem, options) { init(elem, options); } }; })(); //extend jquery $.fn.superPlugin = function(options) { return this.each(function() { var obj = Object.create(myPlugin); //<---lose, lose, lose! obj.init(this, options); $(this).data(obj.pluginName, obj); }); }; }(jQuery, window, document)); 

I watched a few slides at: http://www.slideshare.net/benalman/jquery-plugin-creation from Ben Alman, where he refers to slide 13 on object literals as singleton , and it just baffles me: this is something what this plugin does above, it creates one singleton with without any ability to change its internal state !!!

Also, in the jQuery part, it stores a generic link for each node!

My solution uses factory to preserve the internal state and return the object, and it can also be expanded using the class library and divided into different files:

 ;(function($, window, document, undefined){ var myPluginFactory = function(elem, options){ ........ var modelState = { options: null //collects data from user + default }; ........ function modeler(elem){ modelState.options.a = new $$.A(elem.href); modelState.options.b = $$.B.getInstance(); }; ........ return { pluginName: 'myPlugin', init: function(elem, options) { init(elem, options); }, get_a: function(){return modelState.options.a.href;}, get_b: function(){return modelState.options.b.toString();} }; }; //extend jquery $.fn.myPlugin = function(options) { return this.each(function() { var plugin = myPluginFactory(this, options); $(this).data(plugin.pluginName, plugin); }); }; }(jQuery, window, document)); 

My project: https://github.com/centurianii/jsplugin

See: http://jsfiddle.net/centurianii/s4J2H/1/

0
Dec 6 '13 at 12:23
source share

How about something like that? This is much more clear, but again it would be nice to hear from you if you can improve without unnecessary unnecessary over-simplicity.

 //jQuery plugin Templet (function($){ $.myPlugin = function(options) { //or use "$.fn.myPlugin" or "$.myPlugin" to call it globaly directly from $.myPlugin(); var defaults = { target: ".box", buttons: "li a" }; options = $.extend(defaults, options); function logic(){ //... code goes here } //DEFINE WHEN TO RUN THIS PLUGIN $(window).on('load resize', function () { //load and resize as example ... use what ever you like logic(); }); // RETURN OBJECT FOR CHAINING // return this; // OR FOR FOR MULTIPLE OBJECTS // return this.each(function() { // // Your code ... // }); }; })(jQuery); // USE EXAMPLE with default settings $.myPlugin(); // or run plugin with default settings like so. // USE EXAMPLE with overwriten settings var options = { target: "div.box", // define custom options buttons: ".something li a" // define custom options } $.myPlugin(options); //or run plugin with overwriten default settings 
0
Jan 16 '16 at 19:21
source share



All Articles