Пишем плагин для jQuery
В сети довольно много статей про то, как писать плагины для jQuery, но я решил, что лучше читать первоисточник. А поскольку русской версии мне найти не удалось, и руки чесались что-нибудь попереводить, я попереводил. Оригинал статьи тут, а мой перевод — ниже.
Итак, вы уже разобрались с jQuery и теперь хотели бы научиться писать собственные плагины. Отлично! Вы пришли в правильное место. Расширение возможностей jQuery с помощью плагинов и методов — это очень мощный механизм, который сэкономит вам и вашим коллегам не один час разработки. Эта статья описывает базовые моменты, рекомендации и распространённые ошибки, которые случаются при написании плагина.
Содержание
- 1. Приступая к работе
- 2. Контекст
- 3. Основы
- 4. Цепочки вызовов
- 5. Опции и значения по умолчанию
- 6. Пространства имён
- 7. Заключение и рекомендации
Приступая к работе
Начните писать jQuery-плагин с добавления новой функции-свойства к объекту jQuery.fn
. Название функции и будет названием вашего плагина:
jQuery.fn.myPlugin = function() {
// Творить волшебство здесь
};
Но постойте! Где же чудный значок доллара, к которому я так привык? Он есть, но чтобы убедиться, что ваш плагин не будет конфликтовать с другими библиотеками, которые тоже могут использовать знак доллара, будет лучше сделать так. Передадим объект jQuery
в самовызывающуюся функцию (замыкание), которая привяжет его к знаку доллара, так что никакая другая библиотека не сможет его переопределить в текущем scopе'е исполнения.
(function( $ ){
$.fn.myPlugin = function() {
// Творить волшебство здесь
};
})( jQuery );
Да, так лучше. Теперь внутри этого замыкания мы можем сколько угодно использовать знак доллара вместо jQuery
.
Контекст
Теперь, когда у нас есть заготовка, мы можем начать писать собственно код плагина. Но перед этим, я бы хотел сказать пару слов про контекст. В непосредственном scope'е функции-плагина, ключевое слово this
- это тот самый jQuery-объект, из которого был вызван плагин.
(function( $ ){
$.fn.myPlugin = function() {
// нет необходимости делать $(this), потому что
// "this" уже является jQuery-объектом
// $(this) — это тоже самое, что и $($('#element'));
this.fadeIn('normal', function() {
// this — элемент DOM
});
};
})( jQuery );
$('#element').myPlugin();
Основы
Теперь, когда мы знаем, что такое контекст 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'а
Этот простой плагин возвращает высоту самого высокого div
'а на странице, используя метод .height()
.
Цепочки вызовов
Предыдущий пример возвращает высоту самого высокого 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
. Кроме того, как вы уже могли заметить, передаваемые в вызове плагина аргументы попадают в текущий scope функции-плагина. Так в предыдущем примере строка 'width'
становится аргументом type
внутри функции-плагина.
Опции и значения по умолчанию
Для сложных плагинов, которые обладают большим количеством параметров, рекомендуется задавать значения по умолчанию, которые могут быть переопределены (с помощью метода $.extend
) во время вызова. Таким образом, при вызове плагина можно указать всего один параметр, который заменит соответствующее значение по умолчанию, а не перечислять все возможные параметры. Вот как это делается.
(function( $ ){
$.fn.tooltip = function( options ) {
var settings = {
'location' : 'top',
'background-color' : 'blue'
};
return this.each(function() {
// Если опции существуют, давайте объединим из с нашими значениями по умолчанию
if ( options ) {
$.extend( settings, options );
}
// Здесь идёт код плагина tooltip
});
};
})( jQuery );
$('div').tooltip({
'location' : 'left'
});
В этом примере, после вызова плагина tooltip с заданными параметрами, значение для параметра location
переписывается и становится равным 'left'
, а значение background-color
остаётся как и было 'blue'
. Итоговый объект с параметрами будет выглядеть так:
{
'location' : 'left',
'background-color' : 'blue'
}
Значения по умолчанию — это отличный способ предоставить максимальную гибкость настройки плагина без необходимости указывать все параметры сразу.
Пространства имён
Важная часть разработки плагинов — пространства имён. Правильное применение пространств имён почти наверняка гарантирует, что ваш плагин не будут переопределён другим плагином или кодом, расположенным на той же странице. Так же, с помощью пространств имён разработчику плагинов легче следить за своими методами, событиями и данными.
Методы
Ни при каких обстоятельствах плагин не должен использовать больше одного пространства имён в объекте jQuery.fn
.
(function( $ ){
$.fn.tooltip = function( options ) { // ТАК };
$.fn.tooltipShow = function( ) { // ДЕЛАТЬ };
$.fn.tooltipHide = function( ) { // НЕЛЬЗЯ };
$.fn.tooltipUpdate = function( content ) { // !!! };
})( jQuery );
Такой подход создаёт беспорядок в пространствах имён $.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
есть возможность, о которой мало кто знает, - использование пространств имён для привязки обработчиков событий. Если ваш плагин обрабатывает события, то было бы неплохо для обработчиков указать своё пространство имён. Тогда, если понадобится отвязать эти обработчики от событий, то вы сможете легко это сделать не затрагивая другие обработчики тех же событий. Чтобы назначить пространство имён нужно дописать .namespace
к типу привязываемого события.
(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, происходит привязка обработчика reposition
к событию resize
объекта window
с использованием пространства имён tooltip. Если потом понадобится отключить плагин, то отвязать привязанные им события можно будет передав методу unbind
название пространства имён, в нашем случае - "tooltip". Таким образом можно отвязывать обработчики событий, привязанные плагином, не опасаясь случайно отвязать чужие.
Данные
Зачастую, при разработке плагинов, требуется сохранять состояния или проверять, был ли ваш плагин инициализирован на конкретном DOM-элементе. Удобный способ следить за переменными на уровне отдельных элементов — использование метода jQuery data
. Но, вместо того, чтобы следить за кучей отдельных 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');
// Используем пространства имён FTW
$(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 + ' не существует в jQuery.tooltip' );
}
};
})( jQuery );
Использование data
помогает вам следить за переменными и состояниями между вызовами методов плагина. Хранение всех данных в одном объекте позволяет легко к ним обращаться, и, в случае необходимости, так же легко их удалять.
Заключение и рекомендации
Написание jQuery-плагинов позволяет наиболее эффективно использовать возможности библиотеки. А повторное использование кода экономит ваше время и делает разработку более эффективной. Вот краткие итоги поста, держите их в уме, когда будите писать свой следующий плагин:
- Всегда оборачивайте свой плагин в функцию
(function( $ ){ // здесь сам плагин })( jQuery );
. - Не оборачивайте
this
в непосредственном scope плагина. - Если ваш плагин не должен возвращать важных значений, то всегда возвращайте
this
, чтобы работали цепочки вызовов. - Не запрашивайте большое количество аргументов при вызове плагина, лучше передавать объект, который переопределит настройки по умолчанию.
- Не добавляйте в объект
jQuery.fn
более чем одно пространство имён на плагин. - Используйте пространства имён для ваших методов, событий и данных.
jQuery.fn
читается как "джейКуэри эффин".
{
return this.each(function(){
$(window).bind('resize.tooltip', methods.reposition);});
}
заменить на
{
$(window).bind('resize.tooltip', methods.reposition);
return this;
}
? Извиняюсь заранее, если глупый вопрос.
А первый вариант с return this.each... скорее шаблонный. И, как и все шаблонные решения, он экономит время в ущерб эффективности.
Надеюсь, напишите отзыв на семинар. У нас был этим летом, но я пропустил.
Рекомендую сходить и начинающим и бывалым.