Итак, вы стали комфортно работать с jQuery и хотели бы знать, как написать свой собственный плагин.  Замечательно! Вы там, где нужно. Расширение функционала jQuery по средствам плагинов и методов является очень мощной штукой и может спасти много часов разработки Вам и Вашим коллегам,  заключая Ваши самые часто употребляемые функции в плагины. Этот пост, обрисует в общих чертах,  основы, лучшие методы, и распространенные ошибки, которые надо учитывать при написании Вашего плагина. 

Содержание

 

Приступая к работе

Начнем писать JQuery плагин, с добавления нового метода к объекту jQuery.fn, где имя метода - это название Вашего плагина: 

jQuery.fn.myPlugin = function() {

  // здесь помещаете код Вашего плагина

};

Но постойте! Где же мой знак доллара, который я знаю и люблю?- спросите Вы. Он по прежнему доступен, однако надо убедиться, что Ваш плагин не будет конфликтовать с другими библиотеками, которые также могут использовать знак доллара(Mootools, Prototype). Для этого код Вашего плагина нужно поместить в само вызывающееся замыкание. 

(function( $ ){
  $.fn.myPlugin = function() {
  
    // здесь код Вашего плагина

  };
})( jQuery );

Так-то лучше. После этого, другие библиотеки сколько угодно могут изменять знак $, на Вас это не отразится. Внутри замыкания, $ будет равен jQuery.

 

Контекст

Теперь у нас есть оболочка, и мы можем начать писать фактический код плагина. Но прежде, чем мы это сделаем, я хотел бы сказать несколько слов о контексте. Внутри плагина зарезервированное слово this это ссылка на объект jQuery. Но когда, в коде плагина используются callback функции, то там this - это ссылка на нативный DOM элемент. Ошибки возникают тогда, когда разработчики лишний раз делают $(this) или наоборот забывают это делать

(function( $ ){

  $.fn.myPlugin = function() {
  
    // тут не нужно делать так $(this) потому-что
    // "this" уже является объектом jquery 

    // $(this) будет обозначать то же самое, что $($('#element'));
        
    this.fadeIn('normal', function(){

      // а вот здесь this это ссылка на нативный DOM объект, и чтобы работать с ним 
      // надо сделать так   $(this)

    });

  };
})( jQuery );
$('#element').myPlugin();

 

Hello World!!!

Теперь, когда мы понимаем контекст jQuery плагинов, давайте напишем плагин, который на самом деле что-то делает.

(function( $ ){

  $.fn.maxHeight = function() {
  
    var max = 0;

    this.each(function() {
      max = Math.max( max, $(this).height() );
    });

    return max;
  };
})( jQuery );
var tallest = $('div').maxHeight(); // вернет высоту самого высокого div'а

Это простой плагин, который использует метод  .height()   для возвращения высоты самого высокого DIV'а на странице.

 

Поддержка цепочек вызовов

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

(function( $ ){

  $.fn.lockDimensions = function( type ) {  

    return this.each(function() {

      var $this = $(this);

      if ( !type || type == 'width' ) {
        $this.width( $this.width() );
      }

      if ( !type || type == 'height' ) {
        $this.height( $this.height() );
      }

    });

  };
})( jQuery );
$('div').lockDimensions('width').css('color', 'red');

Поскольку плагин возвращает this в непосредственном теле его функции, он поддерживает цепочку вызовов и jQuery коллекцией можно продолжать манипулировать JQuery методами, такими, как к примеру .css. Так что если Ваш плагин не возвращает ценного значения, Вы должны всегда возвращать this в непосредственном теле функции плагина. Кроме того, параметры которые вы подаете при вызове плагина, подаются как аргументы функции плагина. В примере выше таким аргументом является type, а при вызове в него подается значение 'width'. Кстати, плагин Выше закрепляет высоту или ширину каждого блока на странице, в соответствии с его текущими размерами. 

 

Умолчания и опции

Для более сложных и настраиваемых плагинов, которые поддерживают множество опций, лучшим вариантом будет, иметь настройки по умолчанию, которые могут быть переопределены или дополнены (используя $.extend), при непосредственном вызове плагина. Таким образом, вместо вызова плагина с большим количеством аргументов, Вы можете вызвать его с одним аргументом, который является объектом с полями и значениями, которые Вы бы хотели переопределить в настройках по умолчанию. Вот как это делается.

(function( $ ){

  $.fn.tooltip = function( options ) {  

    var settings = {
      'location'         : 'top',
      'background-color' : 'blue'
    };

    return this.each(function() {        
      // если опции существуют, то совмещаем их
      // со значениями по умолчанию
      if ( options ) { 
        $.extend( settings, options ); // при этом важен порядок совмещения
      }

      //  код плагина тут
     
    });

  };
})( jQuery );
$('div.tools').tooltip({
  'location' : 'left'
});

В этом примере, после вызова плагина tooltip  с соответствующими опциями, в настройках по умолчанию затирается поле location на значение "left". В итоге  Так настройки примут вид:

{
  'location'         : 'left',
  'background-color' : 'blue'
}

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

$('div.tools').tooltip(); // все настройки останутся по умолчанию

 

Пространство имен

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

<div id="myid1">Контент1</div>
alert($('#myid1').html())// сперва метод вернет верное значение Контент1
jQuery.fn.html = function(){
 return 'Hello World';
}
alert($('#myid1').html())// а затем 'Hello World';

 

Методы плагина

Ни при каких обстоятельствах плагин не должен занимать более чем одно имя в  объекте jQuery.fn. 

(function( $ ){

  $.fn.tooltip = function( options ) { // ЭТО };
  $.fn.tooltipShow = function( ) { // ОЧЕНЬ  };
  $.fn.tooltipHide = function( ) { // ПЛОХО  };
  $.fn.tooltipUpdate = function( content ) { // !!!  };

})( jQuery );

Такой код загромождает объект $.fn . Это рекомендуется, поскольку может помех до $. Fn имен. Чтобы исправить это, вы должны собрать все методы Вашего плагина  в один объект, и вызывать метод по его строковому имени

(function( $ ){

  var methods = {
    init : function( options ) { // ЭТО },
    show : function( ) { // УЖЕ  },
    hide : function( ) { // ЛУЧШЕ))) },
    update : function( content ) { // !!! }
  };

  $.fn.tooltip = function( 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 + ' в jQuery.tooltip не существует' );
    }    
  
  };

})( jQuery );
$('div').tooltip(); // вызов метода init
$('div').tooltip({  // вызов метода init с параметрами
  foo : 'bar'
});
$('div').tooltip('hide'); // вызов метода hide
$('div').tooltip('update', 'Это новый контент!'); // вызов метода update с параметрами

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

 

События

Менее известных особенность метода bind является то, что он позволяет для одного пространства имен установить события. Если ваш плагин порождает некие события, хорошей практикой будет, чтобы событие привязывалось к Вашему пространству имен. Таким образом, если Вам нужно отключить  позже, Вы можете сделать это без вмешательства в другие события, которые, возможно, были привязаны к одному типу событий. Вы можете использовать уникальные имена Ваших событий, путем добавления ".<название плагина>".

(function( $ ){

  var methods = {
     init : function( options ) {

       return this.each(function(){
         $(window).bind('resize.tooltip', methods.reposition);
       });

     },
     destroy : function( ) {

       return this.each(function(){
         $(window).unbind('.tooltip');
       })

     },
     reposition : function( ) { // ... },
     show : function( ) { // ... },
     hide : function( ) { // ... },
     update : function( content ) { // ...}
  };

  $.fn.tooltip = function( 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 + ' не существует в jQuery.tooltip' );
    }    
  
  };

})( jQuery );
$('#fun').tooltip();
// некторое время спустя...
$('#fun').tooltip('destroy');

В этом примере, когда плагин tooltip инициализируется методом init, он связывает  метод reposition с событием resize окна в пространстве имен 'tooltip'. Позже, если разработчик должен уничтожить tooltip, мы можем отключить события, связанные c плагином, передав его имя, в данном случае "tooltip", в метод unbind. Это позволяет безопасно отключить события плагина, без случайной отмены привязки событий, которые были связаны с событиями за пределами плагина.

 

Данные

Часто при разработке плагинов, Вам может понадобиться проверить состояние плагина или проверить, инициализирован ли уже Ваш плагин на данный элемент. Использование метода jQuery.data является отличным способом для отслеживания и хранения переменных для каждого элемента. Однако вместо того, чтобы обслуживать кучу отдельных вызовов data с разными именами, лучше использовать один объект содержащий все переменные, и доступ к этому объекту будет через одно имя. 

(function( $ ){

  var methods = {
     init : function( options ) {

       return this.each(function(){
         
         var $this = $(this),
             data = $this.data('tooltip'),
             tooltip = $('<div />', {
               text : $this.attr('title')
             });
         
         // плагин еще не инициализирован
         if ( ! data ) {
         
           /*
             Дополнительные возможности установки       
           */

           $(this).data('tooltip', {
               target : $this,
               tooltip : tooltip
           });

         }
       });
     },
     destroy : function( ) {

       return this.each(function(){

         var $this = $(this),
             data = $this.data('tooltip');

         $(window).unbind('.tooltip');
         data.tooltip.remove();
         $this.removeData('tooltip');

       })

     },
     reposition : function( ) { // ... },
     show : function( ) { // ... },
     hide : function( ) { // ... },
     update : function( content ) { // ...}
  };

  $.fn.tooltip = function( 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 exist on jQuery.tooltip' );
    }    
  
  };

})( jQuery );

Использование data помогает Вам следить за переменными и состоянием плагина из самого плагина. Именование данных в один объект сходное с именем плагина, позволяет легко получить доступ ко всем полям Вашего плагина из одного места, а также легко удалять все связанные с плагином данные, если это будет необходимо. 

 

Резюме

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

  1. Всегда окружайте плагин в замыкание (function( $ ){ // тут Ваш плагин })( jQuery );
  2. Не нужно оборачивать this в теле плагина, это будет избыточным действием
  3. Плагин должен возвращать this в своей функции, если плагин не должен возвращать какого-то конкретного значения, тогда он будет поддерживать цепочки вызовов
  4. Вместо того, чтобы подавать все настройки сразу при запуске плагина, лучше определить их значения по умолчанию, т.о. малознакомый с плагином пользователь сразу сможет проверить его работу
  5. Не создавайте для одного плагина больше одного имени в jQuery.fn 
  6. Всегда используйте пространство имен Вашего плагина при работе с данными и событиями.
  7. jQuery.fn произносится как jQuery effin'

Оригинал

Оставлять комментарии могут только зарегистрированные пользователи

Комментарии  

Роман
# Роман 05.07.2011 15:08
Скажите, как вызвать например метод show из метода init?
vivalaakam
# vivalaakam 31.08.2011 14:11
var methods = {

init: function() {

methods.show();

},

show: function() {

console.log('aloha');

},

}
Руслан
# Руслан 31.05.2012 21:52
this.show();
Андрей
# Андрей 11.07.2011 16:47
Немного не в тему...

Подскажите, а как вы сделали, что при перемещении мышки сбоку появляются узоры? Очень красиво!
игорь
# игорь 26.11.2011 17:43
Пост я не читал, т.к. ето не что я искал, а вот эффект рисовалки ОФИГЕННЫЙ !!!

п.с. я такой видел на сайте mrdoor ))) только он реализован не для заливки а просто)
Ольга
# Ольга 23.01.2012 00:58
Добрый вечер. Подскажите, где в последнем варианте кода задаются параметры по умолчанию?
Leroy
# Leroy 10.06.2012 19:23
этот код опущен, его можно вставить к примеру в метод init
	
init : function( options ) {
var settings = {
'location' : 'top',
'background-color' : 'blue'
};
if ( options )
$.extend(options, settings, options );
// далее все как было
return this.each(function(){
cmygeHm
# cmygeHm 25.06.2012 14:50
Пока только первый вопрос:

"Кстати, плагин Выше закрепляет высоту или ширину каждого блока на странице, в соответствии с его текущими размерами."

Я не понимаю, что это за цель такая. Есть элемент, у которого задана высота и ширина, и мы задаем ему ширину и высоту, те, что уже заданы. Что-то не понимаю?

hello
Leroy
# Leroy 25.06.2012 16:02
Высота и ширина могут быть не заданы а вычисляться динамически, и может меняться от раза к разу, к примеру из-за изменения родительского элемента или из-за изменения содержания. А когда мы проходимся по элементам этим плагином высота и ширина уже не изменятся. Пример надо сказать надуманный на то он и пример, но как говорится случаи бывают разные...
cmygeHm
# cmygeHm 25.06.2012 21:40
Так то понятно. Просто режет немного ухо. Ладно в общем, можно сказать, что имеет право быть такой пример.

Спасибо за статью.
cmygeHm
# cmygeHm 25.06.2012 21:43
А версия для печати есть на сайте?
Leroy
# Leroy 25.06.2012 21:45
нет пока. А зачем она? Пишите такие вопросы мне в профиле
mosesfender
# mosesfender 24.07.2012 03:31
Полезная статья. Немного поплясав с бубном многое понял, и даже накропал себе для админки кое-что в виде плюгина )))



Но вот хоть затрэлись не врублюсь как использовать var settings в init()! Scope показывает settings только в init(). В прочих методах эти поля недоступны. Так-то я решил много проблем, передавая инфу через события. Но ведь чую, что есть какой-то простой доступ к ним внутри методов объекта, коли есть $.extend. Правда, может чего не так сделал?




init: function(options){
var settings = {
datasourceURL: '',
datasource: {},
datatab: {},

columns: [{
field: '',
width: null,
cssclass: '',
cssstyle: '',
title: ''
}]
};

return this.each(function(){
$.extend(settings, options);
$(this).bind('dsUrlChange', methods.setDataSource);
$(this).bind('dsChange', methods.render);
if(options.datasourceURL){
$(this).trigger('dsUrlChange', options.datasourceURL);
}
});
}




Вроде всё правильно, в init() всё устанавливается в соответствии с options, но любое обращение из других методов к этим полям выдаёт undefined. В чём может быть порча?
mosesfender
# mosesfender 24.07.2012 03:51
Чёрт, вот ум замылился! Написал, покурил и сообразил:




return this.each(function(){
$.extend(this, settings);
$.extend(this, options);
$(this).bind('dsUrlChange', methods.setDataSource);
$(this).bind('dsChange', methods.render);
if(options.datasourceURL){
$(this).trigger('dsUrlChange', options.datasourceURL);
}
});




Таким образом переносим settings и options в сам объект, и они доступны через this в методах ))))))))



P.S. Вот ведь запарился с экспериментами, раз уже и так и эдак извращался, изучил четверть методов jQuery, а до самого элементарного не сообразил )))
Ceres
# Ceres 08.07.2013 07:48
буду очень признателен если приведете большую часть кода, эти строки у вас в init?
евпнор
# евпнор 08.02.2013 11:34
Автор мудак, из-за копипаста этой статьи по всему инету, я никак не могу найти то, что должно предшествовать "Итак, вы стали комфортно работать с jQuery". Я ебу в чем эти сраные плагины пишутся и как их запускать?
Leroy
# Leroy 08.02.2013 12:58
вообще это перевод статьи - официального мануала. Это во первых. Во вторых плагины я пишу в блокноте, notepad++. То что вы не знаете как использовать плагины говорит о том что вы еще не комфортно работаете с jQuery. Читайте побольше, хорошая, мощьная библиотека и без плагинов.
Ceres
# Ceres 08.07.2013 06:43
спасибо, за урок.

Подскажите как грамотно построить вызов метода в другом методе одного объекта?

через

$('div').tooltip('hide')

или можно обращаться через

$(this).hide()



не совсем понимаю почему, но почему то привызове $('div').tooltip('hide') this это $('div') но если из этой функци при таком обращений вызвать другой метод this превращается в объект методов, как этого можно избежать?
Leroy
# Leroy 08.07.2013 11:57
this это контекст выполнения, он зависит от того как вызывать и где вызывать. Повлиять на то, чтобы this был тот который нужно можно через apply и call
var obj = {};
var fun = function(){
alert(this);
};
fun(); // window
fun.call(obj); // obj
Ceres
# Ceres 08.07.2013 08:44
что то мне подсказывает что конструкции вида $.extend(this, settings); и $.extend($('#logo'), settings); не будут работать, по моему разумению, объект $(...) this жестко инкапсулированы ,кажется это так называется ,а значит можно воздействовать лишь глобально на объект jquery, но тогда изменения коснуться всех объектов.

верно?
Leroy
# Leroy 08.07.2013 12:01
вы это где вообще увидели? объекты в js и в африке объекты js, поэтому и такой код сработает.Инкапсулируемых методов и свойств в js нет. К чему этот код приведет? понятия не имею.
Ceres
# Ceres 08.07.2013 16:10
не работает
Leroy
# Leroy 08.07.2013 16:22
что не работает? я вам объясняю что такого делать не надо, оно что-то сделает но не пойми что. бред какой-то а не код
Ceres
# Ceres 08.07.2013 20:32
о $.extend(this, settings); писал mosesfender у него все как то четко работало



Смотрите в приведенном примере отображается логика плагинов на jquery это все понятно и хорошо, но допустим, что если нам нужно создать на основе приведенного выше кода, подобие абстрактной фабрики т.е. допустим в примере описана работа с data здесь можно хранить настройки для отдельного объекта и это прекрасно, но что если если нам нужно создать 2 объекта с разными свойствами и методами, т.е. допустим выше описаный пример может служить поражающим объектом для других объектов(у них будут общие свойства, общие методы но будут и индивиудальные). где хранить методы также в data и саый важный вопрос как к ним обращаться?
Leroy
# Leroy 09.07.2013 04:35
теперь ясно, о чем идет тут речь. копировать все методы и свойства settings в this конечно идея интересная, но лучше все же хранить их в data. Методы в js ничем не отличаются от обычных данных.
Ceres
# Ceres 09.07.2013 17:20
да действительно при верном подходе jquery представляет мощный инструмент для реализации ui заменяя половину "петтернов", еще бы знать как технически грамотно формировать инфраструктуру
ColorAnt
# ColorAnt 17.01.2014 14:25
Предлагаю

} else if ( typeof method !== 'object' || ! method ) {

заменить на

} else if ( typeof method !== 'string' || ! method ) {
ColorAnt
# ColorAnt 17.01.2014 14:28
Рано отправился коммент :(

Это необходимо, чтобы не ловить ошибки если пишу несложный плагин и не хочу явно вызывать 'init', позволяя сразу в параметрах вызова передавать calback, например

$("input").delayKeyup(function(event){ ... }, 400);
konstantin
# konstantin 21.12.2019 15:33
спасибо за стаью с хабра