Итак, вы стали комфортно работать с jQuery и хотели бы знать, как написать свой собственный плагин. Замечательно! Вы там, где нужно. Расширение функционала jQuery по средствам плагинов и методов является очень мощной штукой и может спасти много часов разработки Вам и Вашим коллегам, заключая Ваши самые часто употребляемые функции в плагины. Этот пост, обрисует в общих чертах, основы, лучшие методы, и распространенные ошибки, которые надо учитывать при написании Вашего плагина.
Содержание
Контекст
Hello World!!!
Поддержка цепочек вызовов
Умолчания и опции
Пространство имен
Методы плагина
События
Данные
Резюме
Приступая к работе
Начнем писать 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 позволяет Вам объединить Ваши самые удобные функции и любой часто повторяющийся код в одном месте, что позволит сэкономить время и сделать Ваше программирование еще более эффективным. Вот краткое резюме, того, что Вы должны иметь ввиду при разработке своего следующего плагина:
- Всегда окружайте плагин в замыкание (function( $ ){ // тут Ваш плагин })( jQuery );
- Не нужно оборачивать this в теле плагина, это будет избыточным действием
- Плагин должен возвращать this в своей функции, если плагин не должен возвращать какого-то конкретного значения, тогда он будет поддерживать цепочки вызовов
- Вместо того, чтобы подавать все настройки сразу при запуске плагина, лучше определить их значения по умолчанию, т.о. малознакомый с плагином пользователь сразу сможет проверить его работу
- Не создавайте для одного плагина больше одного имени в jQuery.fn
- Всегда используйте пространство имен Вашего плагина при работе с данными и событиями.
- jQuery.fn произносится как jQuery effin'
Комментарии
init: function() {
methods.show();
},
show: function() {
console.log('aloha');
},
}
Подскажите, а как вы сделали, что при перемещении мышки сбоку появляются узоры? Очень красиво!
п.с. я такой видел на сайте mrdoor ))) только он реализован не для заливки а просто)
"Кстати, плагин Выше закрепляет высоту или ширину каждого блока на странице, в соответствии с его текущими размерами."
Я не понимаю, что это за цель такая. Есть элемент, у которого задана высота и ширина, и мы задаем ему ширину и высоту, те, что уже заданы. Что-то не понимаю?
hello
Спасибо за статью.
Но вот хоть затрэлись не врублюсь как использовать var settings в init()! Scope показывает settings только в init(). В прочих методах эти поля недоступны. Так-то я решил много проблем, передавая инфу через события. Но ведь чую, что есть какой-то простой доступ к ним внутри методов объекта, коли есть $.extend. Правда, может чего не так сделал?
Вроде всё правильно, в init() всё устанавливается в соответствии с options, но любое обращение из других методов к этим полям выдаёт undefined. В чём может быть порча?
Таким образом переносим settings и options в сам объект, и они доступны через this в методах ))))))))
P.S. Вот ведь запарился с экспериментами, раз уже и так и эдак извращался, изучил четверть методов jQuery, а до самого элементарного не сообразил )))
Подскажите как грамотно построить вызов метода в другом методе одного объекта?
через
$('div').tooltip('hide')
или можно обращаться через
$(this).hide()
не совсем понимаю почему, но почему то привызове $('div').tooltip('hide') this это $('div') но если из этой функци при таком обращений вызвать другой метод this превращается в объект методов, как этого можно избежать?
верно?
Смотрите в приведенном примере отображается логика плагинов на jquery это все понятно и хорошо, но допустим, что если нам нужно создать на основе приведенного выше кода, подобие абстрактной фабрики т.е. допустим в примере описана работа с data здесь можно хранить настройки для отдельного объекта и это прекрасно, но что если если нам нужно создать 2 объекта с разными свойствами и методами, т.е. допустим выше описаный пример может служить поражающим объектом для других объектов(у них будут общие свойства, общие методы но будут и индивиудальные). где хранить методы также в data и саый важный вопрос как к ним обращаться?
} else if ( typeof method !== 'object' || ! method ) {
заменить на
} else if ( typeof method !== 'string' || ! method ) {
Это необходимо, чтобы не ловить ошибки если пишу несложный плагин и не хочу явно вызывать 'init', позволяя сразу в параметрах вызова передавать calback, например
$("input").delayKeyup(function(event){ ... }, 400);