Como criar um plugin jQuery com methods?

Estou tentando escrever um plugin jQuery que irá fornecer funções / methods adicionais para o object que o chama. Todos os tutoriais que eu li on-line (navegando nas últimas duas horas) incluem, no máximo, como adicionar opções, mas não funções adicionais.

Aqui está o que eu estou procurando fazer:

// formatar div para ser um recipiente de mensagens chamando o plugin para esse div

$("#mydiv").messagePlugin(); $("#mydiv").messagePlugin().saySomething("hello"); 

Ou algo nesse sentido. Aqui está o que resume a: eu chamo o plugin, então eu chamo uma function associada a esse plugin. Eu não consigo encontrar uma maneira de fazer isso, e eu vi muitos plugins fazer isso antes.

Aqui está o que eu tenho até agora para o 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); } }; 

Como posso conseguir algo assim?

Obrigado!


Atualização 18 de novembro de 2013: Eu mudei a resposta correta para a seguinte dos comentários e upvotes de Hari.

De acordo com a página de Autoria de Plugin do jQuery ( http://docs.jquery.com/Plugins/Authoring ), é melhor não confundir os namespaces jQuery e jQuery.fn. Eles sugerem este método:

 (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 ); 

Basicamente, você armazena suas funções em uma matriz (com escopo para a function de quebra automática) e verifica se uma input foi passada por uma string, revertendo para um método padrão (“init” aqui) se o parâmetro for um object (ou nulo).

Então você pode chamar os methods assim …

 $('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 

A variável “arguments” do Javascripts é uma matriz de todos os argumentos transmitidos para que funcione com comprimentos arbitrários dos parâmetros da function.

Aqui está o padrão que usei para criar plugins com methods adicionais. Você usaria como:

 $('selector').myplugin( { key: 'value' } ); 

ou, para invocar um método diretamente,

 $('selector').myplugin( 'mymethod1', 'argument' ); 

Exemplo:

 ;(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); 

E sobre essa abordagem:

 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'); 

Os objects selecionados são armazenados no fechamento messagePlugin, e essa function retorna um object que contém as funções associadas ao plugin, em cada function você pode executar as ações desejadas para os objects atualmente selecionados.

Você pode testar e brincar com o código aqui .

Editar: Código atualizado para preservar o poder da capacidade de cadeia do jQuery.

O jQuery tornou isso muito mais fácil com a introdução da Fábrica de Widgets .

Exemplo:

 $.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 ) { // ... } }); 

Inicialização:

 $('#my-element').myPlugin(); $('#my-element').myPlugin( {defaultValue:10} ); 

Método de chamada:

 $('#my-element').myPlugin('myPublicMethod', 20); 

(É assim que a biblioteca da UI do jQuery é construída.)

O problema com a resposta atualmente selecionada é que você não está realmente criando uma nova instância do plugin personalizado para cada elemento no seletor como você pensa que está fazendo … na verdade, você está apenas criando uma única instância e passando o próprio seletor como o escopo.

Veja este violino para uma explicação mais profunda.

Em vez disso, você precisará percorrer o seletor usando jQuery.each e instanciar uma nova instância do plug-in personalizado para cada elemento no seletor.

Veja como:

 (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); 

E um violino de trabalho .

Você notará como no primeiro violino, todos os divs são sempre movidos para a direita exatamente o mesmo número de pixels. Isso ocorre porque apenas um object de opções existe para todos os elementos no seletor.

Usando a técnica descrita acima, você notará que no segundo violino, cada div não é alinhado e é movido aleatoriamente (excluindo a primeira div como seu random é sempre definido como 1 na linha 89). Isso porque agora estamos instanciando corretamente uma nova instância de plug-in customizado para cada elemento no seletor. Cada elemento tem seu próprio object de opções e não é salvo no seletor, mas na instância do próprio plugin personalizado.

Isso significa que você poderá acessar os methods do plug-in personalizado instanciado em um elemento específico no DOM a partir de novos seletores do jQuery e não será forçado a armazená-los em cache, como você faria no primeiro violino.

Por exemplo, isso retornaria uma matriz de todos os objects de opções usando a técnica no segundo violino. Ele retornaria indefinido no primeiro.

 $('div').customPlugin(); $('div').customPlugin('options'); // would return an array of all options objects 

É assim que você teria que acessar o object options no primeiro violino, e só retornaria um único object, não um array deles:

 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 

Eu sugeriria usar a técnica acima, não a da resposta atualmente selecionada.

Uma abordagem mais simples é usar funções aninhadas. Então você pode encadeá-los de uma maneira orientada a objects. Exemplo:

 jQuery.fn.MyPlugin = function() { var _this = this; var a = 1; jQuery.fn.MyPlugin.DoSomething = function() { var b = a; var c = 2; jQuery.fn.MyPlugin.DoSomething.DoEvenMore = function() { var d = a; var e = c; var f = 3; return _this; }; return _this; }; return this; }; 

E aqui está como chamá-lo:

 var pluginContainer = $("#divSomeContainer"); pluginContainer.MyPlugin(); pluginContainer.MyPlugin.DoSomething(); pluginContainer.MyPlugin.DoSomething.DoEvenMore(); 

Tenha cuidado embora. Você não pode chamar uma function aninhada até que ela tenha sido criada. Então você não pode fazer isso:

 var pluginContainer = $("#divSomeContainer"); pluginContainer.MyPlugin(); pluginContainer.MyPlugin.DoSomething.DoEvenMore(); pluginContainer.MyPlugin.DoSomething(); 

A function DoEvenMore nem sequer existe porque a function DoSomething ainda não foi executada, o que é necessário para criar a function DoEvenMore. Para a maioria dos plugins do jQuery, você realmente só terá um nível de funções aninhadas e não duas, como mostrei aqui.
Apenas certifique-se de que, ao criar funções aninhadas, você defina essas funções no início de sua function pai antes que qualquer outro código na function pai seja executado.

Finalmente, observe que o membro “this” é armazenado em uma variável chamada “_this”. Para funções aninhadas, você deve retornar “_this” se precisar de uma referência à instância no cliente de chamada. Você não pode simplesmente retornar “this” na function aninhada porque isso retornará uma referência para a function e não a instância do jQuery. Retornar uma referência do jQuery permite encadear methods intrínsecos do jQuery ao retornar.

Eu peguei do jQuery Plugin Boilerplate

Também descrito no jQuery Plugin Boilerplate, reprise

 // jQuery Plugin Boilerplate // A boilerplate for jumpstarting jQuery plugins development // version 1.1, May 14th, 2011 // by Stefan Gabos // remember to change every instance of "pluginName" to the name of your plugin! (function($) { // here we go! $.pluginName = function(element, options) { // plugin's default options // this is private property and is accessible only from inside the plugin var defaults = { foo: 'bar', // if your plugin is event-driven, you may provide callback capabilities // for its events. execute these functions before or after events of your // plugin, so that users may customize those particular events without // changing the plugin's code onFoo: function() {} } // to avoid confusions, use "plugin" to reference the // current instance of the object var plugin = this; // this will hold the merged default, and user-provided options // plugin's properties will be available through this object like: // plugin.settings.propertyName from inside the plugin or // element.data('pluginName').settings.propertyName from outside the plugin, // where "element" is the element the plugin is attached to; plugin.settings = {} var $element = $(element), // reference to the jQuery version of DOM element element = element; // reference to the actual DOM element // the "constructor" method that gets called when the object is created plugin.init = function() { // the plugin's final properties are the merged default and // user-provided options (if any) plugin.settings = $.extend({}, defaults, options); // code goes here } // public methods // these methods can be called like: // plugin.methodName(arg1, arg2, ... argn) from inside the plugin or // element.data('pluginName').publicMethod(arg1, arg2, ... argn) from outside // the plugin, where "element" is the element the plugin is attached to; // a public method. for demonstration purposes only - remove it! plugin.foo_public_method = function() { // code goes here } // private methods // these methods can be called only from inside the plugin like: // methodName(arg1, arg2, ... argn) // a private method. for demonstration purposes only - remove it! var foo_private_method = function() { // code goes here } // fire up the plugin! // call the "constructor" method plugin.init(); } // add the plugin to the jQuery.fn object $.fn.pluginName = function(options) { // iterate through the DOM elements we are attaching the plugin to return this.each(function() { // if plugin has not already been attached to the element if (undefined == $(this).data('pluginName')) { // create a new instance of the plugin // pass the DOM element and the user-provided options as arguments var plugin = new $.pluginName(this, options); // in the jQuery version of the element // store a reference to the plugin object // you can later access the plugin and its methods and properties like // element.data('pluginName').publicMethod(arg1, arg2, ... argn) or // element.data('pluginName').settings.propertyName $(this).data('pluginName', plugin); } }); } })(jQuery); 

Tarde demais, mas talvez possa ajudar alguém um dia.

Eu estava na mesma situação, criando um plugin jQuery com alguns methods, e depois de ler alguns artigos e alguns pneus eu criei um clichê do plugin jQuery ( https://github.com/acanimal/jQuery-Plugin-Boilerplate ).

Além disso, desenvolvo com ele um plugin para gerenciar tags ( https://github.com/acanimal/tagger.js ) e escrevi dois posts explicando passo a passo a criação de um plugin jQuery ( http: // acuriousanimal. com / blog / 2013/01/15 / coisas-eu-aprendi-criando-um-jquery-plugin-parte-i / ).

Que tal usar gatilhos? Alguém sabe alguma desvantagem de usá-los? O benefício é que todas as variables ​​internas são acessíveis através dos gatilhos, e o código é muito simples.

Veja no jsfiddle .

Exemplo de uso

 
This is the message container...

Plugar

 jQuery.fn.messagePlugin = function() { return this.each(function() { var lastmessage, $this = $(this); $this.on('messagePlugin.saySomething', function(e, message) { lastmessage = message; saySomething(message); }); $this.on('messagePlugin.repeatLastMessage', function(e) { repeatLastMessage(); }); function saySomething(message) { $this.html("

" + message + "

"); } function repeatLastMessage() { $this.append('

Last message was: ' + lastmessage + '

'); } }); }

Você pode fazer:

 (function ($) { var YourPlugin = function (element, option) { var defaults = { //default value } this.option = $.extend({}, defaults, option); this.$element = $(element); this.init(); } YourPlugin.prototype = { init: function () { }, show: function() { }, //another functions } $.fn.yourPlugin = function (option) { var arg = arguments, options = typeof option == 'object' && option;; return this.each(function () { var $this = $(this), data = $this.data('yourPlugin'); if (!data) $this.data('yourPlugin', (data = new YourPlugin(this, options))); if (typeof option === 'string') { if (arg.length > 1) { data[option].apply(data, Array.prototype.slice.call(arg, 1)); } else { data[option](); } } }); }; }); 

Desta forma, o seu object de plugins é armazenado como valor de dados no seu elemento.

  //Initialization without option $('#myId').yourPlugin(); //Initialization with option $('#myId').yourPlugin({ //your option }); //call show method $('#myId').yourPlugin('show'); 

Aqui eu quero sugerir passos para criar um plugin simples com argumentos.

JS

 (function($) { $.fn.myFirstPlugin = function( options ) { // Default params var params = $.extend({ text : 'Default Title', fontsize : 10, }, options); return $(this).text(params.text); } }(jQuery)); 

Aqui, adicionamos o object padrão chamado params e definimos os valores padrão das opções usando a function extend . Portanto, se passarmos um argumento em branco, ele irá definir os valores padrão, caso contrário, ele será definido.

HTML

 $('.cls-title').myFirstPlugin({ text : 'Argument Title' }); 

Leia mais: Como criar o plugin JQuery

Aqui está a minha versão básica disso. Semelhante aos postados anteriormente, você ligaria como:

 $('#myDiv').MessagePlugin({ yourSettings: 'here' }) .MessagePlugin('saySomething','Hello World!'); 

-ou acesse a instância diretamente @ plugin_MessagePlugin

 $elem = $('#myDiv').MessagePlugin(); var instance = $elem.data('plugin_MessagePlugin'); instance.saySomething('Hello World!'); 

MessagePlugin.js

 ;(function($){ function MessagePlugin(element,settings){ // The Plugin this.$elem = element; this._settings = settings; this.settings = $.extend(this._default,settings); } MessagePlugin.prototype = { // The Plugin prototype _default: { message: 'Generic message' }, initialize: function(){}, saySomething: function(message){ message = message || this._default.message; return this.$elem.html(message); } }; $.fn.MessagePlugin = function(settings){ // The Plugin call var instance = this.data('plugin_MessagePlugin'); // Get instance if(instance===undefined){ // Do instantiate if undefined settings = settings || {}; this.data('plugin_MessagePlugin',new MessagePlugin(this,settings)); return this; } if($.isFunction(MessagePlugin.prototype[settings])){ // Call method if argument is name of method var args = Array.prototype.slice.call(arguments); // Get the arguments as Array args.shift(); // Remove first argument (name of method) return MessagePlugin.prototype[settings].apply(instance, args); // Call the method } // Do error handling return this; } })(jQuery); 

Tente este:

 $.fn.extend({ "calendar":function(){ console.log(this); var methods = { "add":function(){console.log("add"); return this;}, "init":function(){console.log("init"); return this;}, "sample":function(){console.log("sample"); return this;} }; methods.init(); // you can call any method inside return methods; }}); $.fn.calendar() // caller or $.fn.calendar().sample().add().sample() ......; // call methods 

Isso pode realmente ser feito para funcionar de uma maneira “legal” usando defineProperty . Onde “nice” significa sem ter que usar () para obter o namespace do plugin, nem ter que passar o nome da function por string.

Compatibilidade nit: defineProperty não funciona em navegadores antigos, como o IE8 e abaixo. Ressalva: $.fn.color.blue.apply(foo, args) não funciona, você precisa usar foo.color.blue.apply(foo, args) .

 function $_color(color) { return this.css('color', color); } function $_color_blue() { return this.css('color', 'blue'); } Object.defineProperty($.fn, 'color', { enumerable: true, get: function() { var self = this; var ret = function() { return $_color.apply(self, arguments); } ret.blue = function() { return $_color_blue.apply(self, arguments); } return ret; } }); $('#foo').color('#f00'); $('#bar').color.blue(); 

Link JSFiddle

De acordo com o padrão jquery, você pode criar o plugin da seguinte forma:

 (function($) { //methods starts here.... var methods = { init : function(method,options) { this.loadKeywords.settings = $.extend({}, this.loadKeywords.defaults, options); methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); $loadkeywordbase=$(this); }, show : function() { //your code here................. }, getData : function() { //your code here................. } } // do not put semi colon here otherwise it will not work in ie7 //end of methods //main plugin function starts here... $.fn.loadKeywords = function(options,method) { if (methods[method]) { return methods[method].apply(this, Array.prototype.slice.call( arguments, 1)); } else if (typeof method === 'object' || !method) { return methods.init.apply(this, arguments); } else { $.error('Method ' + method + ' does not ecw-Keywords'); } }; $.fn.loadKeywords.defaults = { keyName: 'Messages', Options: '1', callback: '', }; $.fn.loadKeywords.settings = {}; //end of plugin keyword function. })(jQuery); 

Como chamar esse plugin?

 1.$('your element').loadKeywords('show',{'callback':callbackdata,'keyName':'myKey'}); // show() will be called 

Referência: link

Eu acho que isso pode te ajudar …

 (function ( $ ) { $.fn.highlight = function( options ) { // This is the easiest way to have default options. var settings = $.extend({ // These are the defaults. color: "#000", backgroundColor: "yellow" }, options ); // Highlight the collection based on the settings variable. return this.css({ color: settings.color, backgroundColor: settings.backgroundColor }); }; }( jQuery )); 

A seguir está um pequeno plug-in para ter um método de aviso para fins de debugging. Mantenha este código no arquivo jquery.debug.js: JS:

 jQuery.fn.warning = function() { return this.each(function() { alert('Tag Name:"' + $(this).prop("tagName") + '".'); }); }; 

HTML:

   The jQuery Example      

This is paragraph

This is division

Aqui está como eu faço:

 (function ( $ ) { $.fn.gridview = function( options ) { .......... .......... var factory = new htmlFactory(); factory.header(...); ........ }; }( jQuery )); var htmlFactory = function(){ //header this.header = function(object){ console.log(object); } } 

A seguinte estrutura de plugins utiliza o método jQuery- data() para fornecer uma interface pública para plugin-methods / methods internos (preservando a capacidade de jQuery):

 (function($, window, undefined) { $.fn.myPlugin = function(options) { // settings, eg: var settings = $.extend({ elementId: null, shape: "square", color: "aqua", borderWidth: "10px", borderColor: "DarkGray" }, options); // private methods, eg: var setBorder = function(color, width) { settings.borderColor = color; settings.borderWidth = width; drawShape(); }; var drawShape = function() { $('#' + settings.elementId).attr('class', settings.shape + " " + "center"); $('#' + settings.elementId).css({ 'background-color': settings.color, 'border': settings.borderWidth + ' solid ' + settings.borderColor }); $('#' + settings.elementId).html(settings.color + " " + settings.shape); }; return this.each(function() { // jQuery chainability // set stuff on ini, eg: settings.elementId = $(this).attr('id'); drawShape(); // PUBLIC INTERFACE // gives us stuff like: // // $("#...").data('myPlugin').myPublicPluginMethod(); // var myPlugin = { element: $(this), // access private plugin methods, eg: setBorder: function(color, width) { setBorder(color, width); return this.element; // To ensure jQuery chainability }, // access plugin settings, eg: color: function() { return settings.color; }, // access setting "shape" shape: function() { return settings.shape; }, // inspect settings inspectSettings: function() { msg = "inspecting settings for element '" + settings.elementId + "':"; msg += "\n--- shape: '" + settings.shape + "'"; msg += "\n--- color: '" + settings.color + "'"; msg += "\n--- border: '" + settings.borderWidth + ' solid ' + settings.borderColor + "'"; return msg; }, // do stuff on element, eg: change: function(shape, color) { settings.shape = shape; settings.color = color; drawShape(); return this.element; // To ensure jQuery chainability } }; $(this).data("myPlugin", myPlugin); }); // return this.each }; // myPlugin }(jQuery)); 

Agora você pode chamar methods de plugin internos para acessar ou modificar dados de plugins ou o elemento relevante usando esta syntax:

 $("#...").data('myPlugin').myPublicPluginMethod(); 

Contanto que você retorne o elemento atual (isto) de dentro de sua implementação de myPublicPluginMethod() , a capacidade de jQuery será preservada – assim o seguinte funciona:

 $("#...").data('myPlugin').myPublicPluginMethod().css("color", "red").html("...."); 

Aqui estão alguns exemplos (para detalhes, confira este violino ):

 // initialize plugin on elements, eg: $("#shape1").myPlugin({shape: 'square', color: 'blue', borderColor: 'SteelBlue'}); $("#shape2").myPlugin({shape: 'rectangle', color: 'red', borderColor: '#ff4d4d'}); $("#shape3").myPlugin({shape: 'circle', color: 'green', borderColor: 'LimeGreen'}); // calling plugin methods to read element specific plugin settings: console.log($("#shape1").data('myPlugin').inspectSettings()); console.log($("#shape2").data('myPlugin').inspectSettings()); console.log($("#shape3").data('myPlugin').inspectSettings()); // calling plugin methods to modify elements, eg: // (OMG! And they are chainable too!) $("#shape1").data('myPlugin').change("circle", "green").fadeOut(2000).fadeIn(2000); $("#shape1").data('myPlugin').setBorder('LimeGreen', '30px'); $("#shape2").data('myPlugin').change("rectangle", "red"); $("#shape2").data('myPlugin').setBorder('#ff4d4d', '40px').css({ 'width': '350px', 'font-size': '2em' }).slideUp(2000).slideDown(2000); $("#shape3").data('myPlugin').change("square", "blue").fadeOut(2000).fadeIn(2000); $("#shape3").data('myPlugin').setBorder('SteelBlue', '30px'); // etc. ... 

O que você fez foi basicamente estender o object jQuery.fn.messagePlugin pelo novo método. O que é útil, mas não no seu caso.

Você tem que fazer é usar essa técnica

 function methodA(args){ this // refers to object... } function saySomething(message){ this.html(message); to first function } jQuery.fn.messagePlugin = function(opts) { if(opts=='methodA') methodA.call(this); if(opts=='saySomething') saySomething.call(this, arguments[0]); // arguments is an array of passed parameters return this.each(function(){ alert(this); }); }; 

But you can accomplish what you want I mean there is a way to do $(“#mydiv”).messagePlugin().saySomething(“hello”); My friend he started writing about lugins and how to extend them with your chainf of functionalities here is the link to his blog