Как создать плагин jQuery с методами? - PullRequest
186 голосов
/ 13 июля 2009

Я пытаюсь написать плагин jQuery, который предоставит дополнительные функции / методы объекту, который его вызывает. Все учебные пособия, которые я читаю онлайн (просматривал последние 2 часа), включают, как правило, как добавлять опции, но не дополнительные функции.

Вот что я хочу сделать:

// форматировать div, чтобы он был контейнером сообщений, вызывая плагин для этого div

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

или что-то в этом роде. Вот что сводится к следующему: я вызываю плагин, затем вызываю функцию, связанную с этим плагином. Кажется, я не могу найти способ сделать это, и я видел много плагинов, делающих это раньше.

Вот что у меня есть для плагина:

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

Как мне достичь чего-то подобного?

Спасибо!


Обновление от 18 ноября 2013 г .: я изменил правильный ответ на следующие комментарии и комментарии Хари.

Ответы [ 20 ]

304 голосов
/ 29 июля 2011

Согласно странице авторизации плагина jQuery (http://docs.jquery.com/Plugins/Authoring),, лучше не путать пространства имен jQuery и jQuery.fn. Они предлагают этот метод:

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

Обычно вы сохраняете свои функции в массиве (ограниченном функцией обтекания) и проверяете наличие записи, если переданный параметр является строкой, возвращаясь к методу по умолчанию (здесь «init»), если параметр является объектом (или нуль).

Тогда вы можете вызывать методы так ...

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

Переменная «arguments» Javascripts представляет собой массив всех переданных аргументов, поэтому она работает с произвольными длинами параметров функции.

55 голосов
/ 13 июля 2009

Вот шаблон, который я использовал для создания плагинов с дополнительными методами. Вы бы использовали это как:

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

или, чтобы вызвать метод напрямую,

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

Пример:

;(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);
35 голосов
/ 13 июля 2009

А как насчет этого подхода:

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

Выбранные объекты хранятся в закрытии messagePlugin, и эта функция возвращает объект, который содержит функции, связанные с плагином, в каждой функции вы можете выполнять нужные действия с выбранными в данный момент объектами.

Вы можете проверить и поиграть с кодом здесь .

Редактировать: Обновлен код, позволяющий сохранить возможности цепочки jQuery.

17 голосов
/ 10 апреля 2014

Проблема с выбранным в данный момент ответом заключается в том, что вы фактически не создаете новый экземпляр пользовательского плагина для каждого элемента в селекторе, как вы думаете, вы делаете ... вы фактически создаете только один экземпляр и передавая сам селектор в качестве области видимости.

Просмотр этой скрипки для более глубокого объяснения.

Вместо этого вам нужно перебрать селектор с помощью jQuery.each и создать новый экземпляр пользовательского плагина для каждого элемента в селекторе.

Вот как:

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

И рабочая скрипка .

Вы заметите, что в первой скрипке все div всегда перемещаются вправо на одинаковое количество пикселей. Это связано с тем, что для всех элементов селектора существует только один объект параметров.

Используя вышеописанную технику, вы заметите, что во втором скрипте каждый div не выровнен и перемещается случайным образом (исключая первый div, поскольку его рандомизатор всегда установлен в 1 в строке 89). Это потому, что мы сейчас правильно создаем экземпляр нового пользовательского экземпляра плагина для каждого элемента в селекторе. Каждый элемент имеет свой собственный объект параметров и сохраняется не в селекторе, а в случае самого пользовательского плагина.

Это означает, что вы сможете получить доступ к методам пользовательского плагина, созданного на конкретном элементе в DOM, из новых селекторов jQuery и не будете вынуждены их кэшировать, как если бы вы были в первой скрипке. 1024 *

Например, это вернуло бы массив всех объектов параметров, используя технику во второй скрипке. В первом случае он вернет undefined.

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

Вот как вы должны получить доступ к объекту параметров в первом скрипте и возвращать только один объект, а не их массив:

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

Я бы предложил использовать вышеописанную технику, а не ту, которая выбрана в данный момент.

15 голосов
/ 28 июля 2013

jQuery сделал это намного проще с введением фабрики виджетов .

Пример:

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

Вызов метода:

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

(Так создается библиотека jQuery UI .)

13 голосов
/ 26 июня 2010

Более простой подход - использовать вложенные функции. Затем вы можете связать их объектно-ориентированным способом. Пример:

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;
};

А вот как это назвать:

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

Будьте осторожны, хотя. Вы не можете вызывать вложенную функцию, пока она не будет создана. Таким образом, вы не можете сделать это:

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

Функция DoEvenMore даже не существует, поскольку функция DoSomething еще не была запущена, что необходимо для создания функции DoEvenMore. Для большинства плагинов jQuery у вас действительно будет только один уровень вложенных функций, а не два, как я показал здесь.
Просто убедитесь, что при создании вложенных функций вы определяете эти функции в начале их родительской функции, прежде чем будет выполнен любой другой код в родительской функции.

Наконец, обратите внимание, что элемент "this" хранится в переменной с именем "_this". Для вложенных функций вы должны вернуть «_this», если вам нужна ссылка на экземпляр в вызывающем клиенте. Вы не можете просто вернуть «this» во вложенной функции, потому что она будет возвращать ссылку на функцию, а не экземпляр jQuery. Возврат ссылки jQuery позволяет вам связать внутренние методы jQuery по возвращении.

9 голосов
/ 23 июня 2014

Я получил это от jQuery Plugin Boilerplate

Также описано в jQuery Plugin Boilerplate, повтор

// 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);
6 голосов
/ 10 мая 2014

Слишком поздно, но, может быть, это поможет кому-нибудь однажды.

Я был в такой же ситуации, как и при создании плагина jQuery с некоторыми методами, и после прочтения некоторых статей и некоторых шин я создал шаблон jQuery-плагина (https://github.com/acanimal/jQuery-Plugin-Boilerplate).

Кроме того, я разработал с его помощью плагин для управления тегами (https://github.com/acanimal/tagger.js)) и написал две публикации в блоге, шаг за шагом объясняющие создание плагина jQuery (http://acuriousanimal.com/blog/2013/01/15/things-i-learned-creating-a-jquery-plugin-part-i/).

4 голосов
/ 07 марта 2016

Вы можете сделать:

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

Таким образом ваш объект плагинов сохраняется как значение данных в вашем элементе.

 //Initialization without option
 $('#myId').yourPlugin();

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

//call show method
$('#myId').yourPlugin('show');
3 голосов
/ 27 августа 2013

Как насчет использования триггеров? Кто-нибудь знает какой-либо недостаток, используя их? Преимущество заключается в том, что все внутренние переменные доступны через триггеры, а код очень прост.

См. jsfiddle .

Пример использования

<div id="mydiv">This is the message container...</div>

<script>
    var mp = $("#mydiv").messagePlugin();

    // the plugin returns the element it is called on
    mp.trigger("messagePlugin.saySomething", "hello");

    // so defining the mp variable is not needed...
    $("#mydiv").trigger("messagePlugin.repeatLastMessage");
</script>

Plugin

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("<p>" + message + "</p>");
        }

        function repeatLastMessage() {
            $this.append('<p>Last message was: ' + lastmessage + '</p>');
        }

    });

}
...