How to create a jQuery plugin with methods?
Solution 1
According to the jQuery Plugin Authoring page (http://docs.jquery.com/Plugins/Authoring), it's best not to muddy up the jQuery and jQuery.fn namespaces. They suggest this method:
(function( $ ){
var methods = {
init : function(options) {
},
show : function( ) { },// IS
hide : function( ) { },// GOOD
update : function( content ) { }// !!!
};
$.fn.tooltip = function(methodOrOptions) {
if ( methods[methodOrOptions] ) {
return methods[ methodOrOptions ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) {
// Default to "init"
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + methodOrOptions + ' does not exist on jQuery.tooltip' );
}
};
})( jQuery );
Basically you store your functions in an array (scoped to the wrapping function) and check for an entry if the parameter passed is a string, reverting to a default method ("init" here) if the parameter is an object (or null).
Then you can call the methods like so...
$('div').tooltip(); // calls the init method
$('div').tooltip({ // calls the init method
foo : 'bar'
});
$('div').tooltip('hide'); // calls the hide method
$('div').tooltip('update', 'This is the new tooltip content!'); // calls the update method
Javascripts "arguments" variable is an array of all the arguments passed so it works with arbitrary lengths of function parameters.
Solution 2
Here's the pattern I have used for creating plugins with additional methods. You would use it like:
$('selector').myplugin( { key: 'value' } );
or, to invoke a method directly,
$('selector').myplugin( 'mymethod1', 'argument' );
Example:
;(function($) {
$.fn.extend({
myplugin: function(options,arg) {
if (options && typeof(options) == 'object') {
options = $.extend( {}, $.myplugin.defaults, options );
}
// this creates a plugin for each element in
// the selector or runs the function once per
// selector. To have it do so for just the
// first element (once), return false after
// creating the plugin to stop the each iteration
this.each(function() {
new $.myplugin(this, options, arg );
});
return;
}
});
$.myplugin = function( elem, options, arg ) {
if (options && typeof(options) == 'string') {
if (options == 'mymethod1') {
myplugin_method1( arg );
}
else if (options == 'mymethod2') {
myplugin_method2( arg );
}
return;
}
...normal plugin actions...
function myplugin_method1(arg)
{
...do method1 with this and arg
}
function myplugin_method2(arg)
{
...do method2 with this and arg
}
};
$.myplugin.defaults = {
...
};
})(jQuery);
Solution 3
What about this approach:
jQuery.fn.messagePlugin = function(){
var selectedObjects = this;
return {
saySomething : function(message){
$(selectedObjects).each(function(){
$(this).html(message);
});
return selectedObjects; // Preserve the jQuery chainability
},
anotherAction : function(){
//...
return selectedObjects;
}
};
}
// Usage:
$('p').messagePlugin().saySomething('I am a Paragraph').css('color', 'red');
The selected objects are stored in the messagePlugin closure, and that function returns an object that contains the functions associated with the plugin, the in each function you can perform the desired actions to the currently selected objects.
You can test and play with the code here.
Edit: Updated code to preserve the power of the jQuery chainability.
Solution 4
The problem with the currently selected answer is that you're not actually creating a new instance of the custom plugin for every element in the selector like you think you're doing... you're actually only creating a single instance and passing in the selector itself as the scope.
View this fiddle for a deeper explanation.
Instead, you'll need to loop through the selector using jQuery.each and instantiate a new instance of the custom plugin for every element in the selector.
Here's how:
(function($) {
var CustomPlugin = function($el, options) {
this._defaults = {
randomizer: Math.random()
};
this._options = $.extend(true, {}, this._defaults, options);
this.options = function(options) {
return (options) ?
$.extend(true, this._options, options) :
this._options;
};
this.move = function() {
$el.css('margin-left', this._options.randomizer * 100);
};
};
$.fn.customPlugin = function(methodOrOptions) {
var method = (typeof methodOrOptions === 'string') ? methodOrOptions : undefined;
if (method) {
var customPlugins = [];
function getCustomPlugin() {
var $el = $(this);
var customPlugin = $el.data('customPlugin');
customPlugins.push(customPlugin);
}
this.each(getCustomPlugin);
var args = (arguments.length > 1) ? Array.prototype.slice.call(arguments, 1) : undefined;
var results = [];
function applyMethod(index) {
var customPlugin = customPlugins[index];
if (!customPlugin) {
console.warn('$.customPlugin not instantiated yet');
console.info(this);
results.push(undefined);
return;
}
if (typeof customPlugin[method] === 'function') {
var result = customPlugin[method].apply(customPlugin, args);
results.push(result);
} else {
console.warn('Method \'' + method + '\' not defined in $.customPlugin');
}
}
this.each(applyMethod);
return (results.length > 1) ? results : results[0];
} else {
var options = (typeof methodOrOptions === 'object') ? methodOrOptions : undefined;
function init() {
var $el = $(this);
var customPlugin = new CustomPlugin($el, options);
$el.data('customPlugin', customPlugin);
}
return this.each(init);
}
};
})(jQuery);
And a working fiddle.
You'll notice how in the first fiddle, all divs are always moved to the right the exact same number of pixels. That is because only one options object exists for all elements in the selector.
Using the technique written above, you'll notice that in the second fiddle, each div is not aligned and is randomly moved (excluding the first div as it's randomizer is always set to 1 on line 89). That is because we are now properly instantiating a new custom plugin instance for every element in the selector. Every element has its own options object and is not saved in the selector, but in the instance of the custom plugin itself.
This means that you'll be able to access the methods of the custom plugin instantiated on a specific element in the DOM from new jQuery selectors and aren't forced to cache them, as you would be in the first fiddle.
For example, this would return an array of all options objects using the technique in the second fiddle. It would return undefined in the first.
$('div').customPlugin();
$('div').customPlugin('options'); // would return an array of all options objects
This is how you would have to access the options object in the first fiddle, and would only return a single object, not an array of them:
var divs = $('div').customPlugin();
divs.customPlugin('options'); // would return a single options object
$('div').customPlugin('options');
// would return undefined, since it's not a cached selector
I'd suggest using the technique above, not the one from the currently selected answer.
Solution 5
jQuery has made this a lot easier with the introduction of the Widget Factory.
Example:
$.widget( "myNamespace.myPlugin", {
options: {
// Default options
},
_create: function() {
// Initialization logic here
},
// Create a public method.
myPublicMethod: function( argument ) {
// ...
},
// Create a private method.
_myPrivateMethod: function( argument ) {
// ...
}
});
Initialization:
$('#my-element').myPlugin();
$('#my-element').myPlugin( {defaultValue:10} );
Method calling:
$('#my-element').myPlugin('myPublicMethod', 20);
(This is how the jQuery UI library is built.)
Related videos on Youtube
Yuval Karmi
Updated on March 07, 2022Comments
-
Yuval Karmi about 2 years
I'm trying to write a jQuery plugin that will provide additional functions/methods to the object that calls it. All the tutorials I read online (have been browsing for the past 2 hours) include, at the most, how to add options, but not additional functions.
Here's what I am looking to do:
//format div to be a message container by calling the plugin for that div
$("#mydiv").messagePlugin(); $("#mydiv").messagePlugin().saySomething("hello");
or something along those lines. Here's what it boils down to: I call the plugin, then I call a function associated with that plugin. I can't seem to find a way to do this, and I've seen many plugins do it before.
Here's what I have so far for the plugin:
jQuery.fn.messagePlugin = function() { return this.each(function(){ alert(this); }); //i tried to do this, but it does not seem to work jQuery.fn.messagePlugin.saySomething = function(message){ $(this).html(message); } };
How can I achieve something like that?
Thank you!
Update Nov 18, 2013: I've changed the correct answer to that of Hari's following comments and upvotes.
-
Yuval Karmi almost 15 yearsthis seems like a non-standard way of doing things - is there anything simpler than this, like chaining functions? thank you!
-
Yuval Karmi almost 15 yearsalso, how would i pass more than one argument under the arg - as an object, perhaps? (i.e. $('selector').myplugin('mymethod1', {arg1 : 'arg1value', arg2 : 'arg2value}) ). thank you!
-
Yuval Karmi almost 15 yearsone potential problem(?) that i noticed: when creating the new plugin new $.myplugin(this, options, arg), you pass 3 arguments, while the plugin itself seems to only expect 2 (i.e $.myplugin = function( options, arg)) - is this done on purpose? if not, how do i fix it? thanks!
-
Yuval Karmi almost 15 yearsi am having a bit of a hard time understanding what this would look like. Assuming i have code that needs to be executed the first time this is run, i'll have to first initialize it in my code - something like this: $('p').messagePlugin(); then later in the code i'd like to call the function saySomething like this $('p').messagePlugin().saySomething('something'); will this not re-initialize the plugin and then call the function? what would this look like with the enclosure and options? thank you very much. -yuval
-
tvanfosson almost 15 years@yuval -- typically jQuery plugins return jQuery or a value, not the plugin itself. That's why the name of the method is passed as an argument to the plugin when you want to invoke the plugin. You could pass any number of arguments, but you'll have to adjust the functions and the argument parsing. Probably best to set them in an anonymous object as you showed.
-
tvanfosson almost 15 years@yuval -- a transliteration mistake when extracting the code from an actual plugin I have. I've corrected it.
-
tvanfosson almost 15 yearsSort of breaks the chainability paradigm of jQuery, though.
-
Dragouf almost 13 yearsmaybe this should be the best answer
-
w00t almost 13 yearsThis is great - I only wonder why jQuery seems to favor calling the methods by name as in the .plugin('method') pattern?
-
w00t almost 13 yearsEvery time you call messagePlugin() it will create a new object with those two functions, no?
-
adrian.andreas over 12 yearsThis is the method I use. You can also call the methods staticly via $.fn.tooltip('methodname', params);
-
Hari Honor over 12 yearsFor this interested in a more javascript class-like version of the jQuery plugin template have a look here: club15cc.com/post/11690384272/… Amongst other things, it improves on the jquery.com version by allowing for class properties which maintain state been method calls.
-
aditya over 12 years@tvanfosson: I've created a jsfiddle using the above template. However, a
get()
method doesn't return the value of an internal variable. Could you tell me what I'm doing wrong? jsfiddle.net/aditya/FCwRs -
tvanfosson over 12 years@aditya (1) the variable isn't defined at the point in the function that you are invoking the get() function. (2) you don't want to define the variable in the function or it will get reinitialized each time the function is invoked. It's probably better to define it on the plugin itself and reference it. See this updated fiddle: jsfiddle.net/JxqHF
-
tvanfosson over 12 years@aditya or better yet, store it as data on the element: jsfiddle.net/CmmrS
-
aditya over 12 years@tvanfosson In that case, isn't stackoverflow.com/q/6871820/56121 a cleaner way of doing it using jQuery
.data()
rather than adding static vars to your function? -
tvanfosson over 12 years@aditya that's what I did in my second fiddle. It would really depend on whether it's a global plugin option or an option specific to a particular instance of a plugin. If global, like the defaults, adding it to the plugin definition is appropriate. If it's specific to that instance, storing it as data on the element is a better way to go. The approach for functions could be done either using the array technique or inline within the function definition. Either way they're scoped local to the plugin.
-
Kevin Beal almost 12 yearsI'm using this approach myself. Thanks!
-
GusDeCooL over 11 yearsWhat is the meaning of
;
in your first line? please explain to me :) -
tvanfosson over 11 years@GusDeCooL it just makes sure that we're starting a new statement so that our function definition isn't interpreted as an argument to someone else's poorly formatted Javascript (i.e, the initial paren isn't taken as a function invocation operator). See stackoverflow.com/questions/7365172/…
-
Kalzem about 11 yearsWhen inside a "myplugin_method", how can I have access to the var of the plugin ?
-
tvanfosson about 11 years@BabyAzerty - not quite sure what you mean. Any variable declared within the
$.myplugin
function should be available (within scope) of any functions also defined within it. -
Kalzem about 11 yearsIn the $.myplugin.defaut, I have 2 var (data and height). How can I have access to these var within the plugin's methods ? I tried "height", "myplugin.height","$.myplugin.height" none work...
-
tvanfosson about 11 years@BabyAzerty
defaults.data
or$.myplugin.defaults.data
, similarly withheight
, although, these actually get stuff intooptions
so you could useoptions.data
andoptions.height
if you want the, possibly, user-overridden versions. -
Kalzem about 11 yearsI tried accessing options inside my plugin methods, but I cannot get any variables (data nor height). Here is a fiddle jsfiddle.net/uWxcf
-
Joshua Bambrick almost 11 yearsThe main issue with this approach is that it cannot preserve chainability following
$('p').messagePlugin()
unless you call one of the two functions it returns. -
Courtney Maroney almost 11 yearsThis does not work. If you invoke the plugin on two different containers, the internal variables get overridden (namely _this)
-
ivkremer almost 11 yearsVery handy architecture. I also added this line before calling the init method:
this.data('tooltip', $.extend(true, {}, $.fn.tooltip.defaults, methodOrOptions));
, so now I can access to options whenever I want after the initialization. -
streetlogics over 10 yearsFor any like me who first said "where did the arguments variable come from" - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… - I've been using JS forever and never knew that. You learn something new everyday!
-
István Ujj-Mészáros over 10 years@tvanfosson What do you think about my aproach? Do you know any drawback? stackoverflow.com/a/18455244/504270
-
tvanfosson over 10 yearscf. your comment. The only problem I see here is arguably a misuse of the event system. It's atypical to use events purely to invoke a function; it seems like overkill and may be easily broken. Normally, you'd use events in a publish-subscribe fashion, e.g., a function publishes that some condition "A" has occurred. Other entities, interested in "A", listen for the message that "A" has happened, then do something. You seem to be using it as push "command" instead, but assuming there is only one listener. You'd want to be careful that your semantics aren't broken by (others) adding listeners.
-
István Ujj-Mészáros over 10 years@tvanfosson Thanks for your comment. I understand that it is not common technique and it can cause problems if someone accidentally add an event listener, but if it is named after the plugin, then it is very unlikely. I don't know about any performance related issues, but the code itself seems to be much simpler to me than with the other solutions, but I may be missing something.
-
Maciej Gurban over 10 yearsAssuming I define my configuration defaults (which can be overwritten by passing object on plugin init), how do I go about accessing their values? Or do I have to create methods/getters for each property?
-
daniel.sedlacek about 10 yearsThis is very bad architecture. It is error prone, can not be strong typed (with TypeScript) or checked for integrity at compile time. There is a reason why we don't write code in strings.
-
Yarin about 10 years@daniel.sedlacek a) "very bad architecture" - it's jQuery's standard widget architecture b) "checked for integrity at compile time" - JavaScript is a dynamic language c) "TypeScript" - wha?
-
daniel.sedlacek about 10 yearsa) that's argumentum ad populum, b) every better JS IDE has code completion or linting, c) google it
-
mystrdat about 10 yearsThat is pure delusion, Mr. Sedlacek.
-
Peter Krauss about 10 years@CMS, Please, I have another problem here at Stackoverflow, with chain method; but even using your recipe (returning the
selectedObjects
variable), it not works, see here a jsfiddle. -
Stephen Collins about 10 years@DiH, I'm with you on this one. This approach seems great, but it doesn't give you access to your global settings from anywhere other than
init
. -
Kevin Jurkowski about 10 yearsThere's a major problem with this technique! It doesn't create a new instance for every element in the selector like you think you're doing, instead it creates only a single instance attached to the selector itself. View my answer for a solution.
-
dalemac about 10 yearsThanks, this helped me a lot, particularly introducing the .data() method to me. Very handy. FWIW you can also simplify some of your code by using anonymous methods.
-
jakabadambalazs almost 10 years@KevinJurkowski - Yes and no - You are correct that this method does not create a new instance for each element but it is not always (I'd say most of the time not) necessary to have a separate instance for each element. @HariKaramSingh - QUESTION: Why would you have methods object
scoped to the wrapping function
as opposed to having it inside the plugin body? -
Hari Honor almost 10 yearsNot my decision. That code is from jQuery's site (or least the version from several years ago!). jQuery didn't seem concerned with having an object-based state as is typical in OOP. That's why I made this more complex but stateful version: gist.github.com/Club15CC/1300890 It's been a LONG time since I've looked at this so please be kind!
-
Frondor over 8 yearsAs stated in mozilla documentation: You should not slice on arguments because it prevents optimizations in JavaScript engines (V8 for example). Instead, try constructing a new array by iterating through the arguments object. More information.
-
Dex Dave over 7 yearspossibly the best post I have come across yet about creating jQuery plugins as a beginner - thanks ;)
-
Sakthi Karthik over 7 yearsHi Gopal Joshi , Please give next level jquery plugin creation. we expect from your needful answer.
-
Gopal Joshi over 7 yearsHello @SakthiKarthik, Off cource I will publish new tutorial soon in my blog
-
Gopal Joshi over 7 yearsHi @SakthiKarthik, You may refer new article on next level jquery plugin here sgeek.org/…
-
Airn5475 about 7 yearsPer docs: This system is called the Widget Factory and is exposed as jQuery.widget as part of jQuery UI 1.8; however, it can be used independently of jQuery UI. How is $.widget used without jQuery UI?
-
Alex G about 7 yearsjQuery chainability is not working using this method...
$('.my-elements').find('.first-input').customPlugin('update', 'first value').end().find('.second-input').customPlugin('update', 'second value'); returns Cannot read property 'end' of undefined
. jsfiddle.net/h8v1k2pL -
Alex G about 7 yearsYour method breaks jQuery chaining:
$('.first-input').data('pluginName').publicMethod('new value').css('color', red);
returnsCannot read property 'css' of undefined
jsfiddle.net/h8v1k2pL/1 -
Qian Chen about 6 yearsHow should methods access the options?
-
jslearner07 over 5 yearsI am also using the same approach. Just wondering if we can default one method? Is it possible? Ex $('p').messagePlugin() should call saySomething directly. However 2nd method needs to be called as given below. $('p').messagePlugin().anotherAction(). Thanks.
-
Paul Swetz over 5 yearsFail: does not allow pluginContainer.MyPlugin.DoEvenMore().DoSomething();
-
CrandellWS over 5 years@AlexG given this example you would add
return $element
so in this example you would change it toplugin.foo_public_method = function() {/* Your Code */ return $element;}
@Salim thanks for helping me ... github.com/AndreaLombardo/BootSideMenu/pull/34 -
rickvian over 3 yearshi guys, how do i call myplugin_method1(arg) in the example?
-
user3700562 about 3 yearsThank you for this SANE approach. Creating jQuery plugins correctly is just way too ridiculously complicated. This really is a major weakness of jQuery and of Javascript as a whole with it's prototype weirdness.