Все jQuery UI виджеты создаются на простой основе — Фабрике Виджетов. Она обеспечивает гибкую основу для создания сложных, структурированных плагинов с совместимым API. С её помощью можно создавать не только плагины jQuery UI, но и любые объектно-ориентированные компоненты, не изобретая велосипедов. Она не зависит от других компонентов jQuery UI, наоборот, большая часть компонентов UI зависит от неё.
Фабрика виджетов это метод глобального объекта jQuery — jQuery.widget — принимающий 2 или 3 аргумента.
Первый аргумент строкового типа, содержащий пространство имен для вашего виджета, и его имя, разделяются точкой. Пространство имен обязательно, и ссылается на место в глобальном объекте jQuery, где будет храниться прототип виджета. Если пространство имен не задано, фабрика создаст вам его. (прим. переводчика — Но плагин без неймспейса попросту не заработает, во всяком случае у меня не получилось. Да и вообще неймспейсы полезно.) Имя плагина хранится как имя плагина и прототипа. Например:
создаст
Второй (не обязательный) аргумент, прототип для наследования от него. Например, в jQuery UI есть плагин «mouse», на котором основаны все плагины для взаимодействий [с мышью]. Для достижения этого,
Если вы не указываете этот аргумент, виджет наследуется напрямую от «основного виджета» jQuery.Widget (обратите вниманию на разницу между
Последний аргумент — литерал объекта, который будет использоваться в качестве прототипа для каждого экземпляра виджета. Фабрика создает цепочку прототипов, соединяя прототип виджета со всеми виджетами от которого он наследуется, вплоть до базового
При вызове jQuery.widget в прототипе jQuery (jQuery.fn) создается новый метод, соответствующий имени виджета, в нашем случае это будет
Упрощенный подход, описанный в Гайдлайнах по разработке плагина, оставляет много вопросов с конкретной реализацией, когда дело касается структурированного кода и ООП ориентированных плагинах. Кроме того, они не предлагают никаких решений для общих задач. Фабрика виджетов предоставляет API jQuery UI, для связи с экземпляром плагина и решения нескольких повторяющихся задач.
Литерал объекта, передаваемый в качестве прототипа может быть устроен как угодно, но он обязательно должен содержать
Ваш объект скорей всего будет содержать методы, для обработки различных специфичных операций, например построение и вставка новых элементов или обработка событий. Разумно было бы использовать изолированные методы для каждой операции, вместо обработки кучи всего в методе
Например, в гипотетическом виджете, расширяющим
К сожалению, если оставить так код в
Как вы наверное заметили, некоторые методы мы написали с нижним подчеркиванием вначале, в то время как некоторые без него. Методы с префиксом обрабатываются как «приватные». Фабрика блокирует все попытки вызвать их через
Код выше вызовет возбуждение исключения. Но, раз они присутствуют в прототипе виджета, они считаются приватными только по соглашению. При вызове через
Как же принять правильное решение? Если пользователям ваших виджетов понадобятся определенные методы, то сделайте их публичными. Пример с
Элемент, используемый экземпляром плагина. Например:
В этом случае
Опции, используемые для настройки плагина. При создании экземпляра, любые опции, переданные пользователем, автоматически объединяются с заданными в
Пространство имен плагина, в нашем случае «demo». Как правило не используется в плагинах.
Имя плагина, в нашем случае «multi». Немного полезней чем
Свойство, использующееся для именования событий плагина. Например,
Полезно для создания имен классов элементов виджета. Например, если нужно пометить элемент как активный
Для большинства плагинов это не обязательно, потому что проще написать что-то вроде
(прим. переводчика - лучше всего, конечно, ознакомиться напрямую с документацией)
Метод, в котором настраивается всё, относящееся к вашему виджету — создание элементов, навешивание событий и т. д. Метод вызывается один раз, сразу после создания экземпляра.
Метод, вызывающийся каждый раз при вызове виджета, вне зависимости от количества передаваемых аргументов. Во время первого вызова
Метод, уничтожающий экземпляр плагина и проводящий другие необходимые вам действия. Все модификации, которые делает ваш плагин должны уничтожаться методом
Используется для задания опций после создания экземпляра. Сигнатура метода похожа на методы
Приватный метод, используется для установки настроек после создания экземпляра. Метод вызывает _setOption, поэтому он не должен меняться сторонними плагинами.
Вызывается, когда пользователь меняет значение через метод
Вызывая родительский метод
Хелпер, вызывающий
в вашем
Хелпер, вызывающий
в вашем _setOption.
Этот метод необходимо использовать для всех коллбеков. Имя выполняемого коллбека единственный обязательный параметр. Все коллбеки также триггерят событие (см. описание
Что это?
Фабрика виджетов это метод глобального объекта jQuery — jQuery.widget — принимающий 2 или 3 аргумента.
jQuery.widget(
'namespace.widgetname',
namespace.superwidget, // не обязательно - прототип существующего виджета
// от которого будет наследоваться ваш виджет
{...} // литерал объекта,
// который послужит прототипом вашего виджета
);
Первый аргумент строкового типа, содержащий пространство имен для вашего виджета, и его имя, разделяются точкой. Пространство имен обязательно, и ссылается на место в глобальном объекте jQuery, где будет храниться прототип виджета. Если пространство имен не задано, фабрика создаст вам его. (прим. переводчика — Но плагин без неймспейса попросту не заработает, во всяком случае у меня не получилось. Да и вообще неймспейсы полезно.) Имя плагина хранится как имя плагина и прототипа. Например:
jQuery.widget("demo.multi", {...})
создаст
jQuery.demo.multi
и jQuery.demo.multi.prototype
.Второй (не обязательный) аргумент, прототип для наследования от него. Например, в jQuery UI есть плагин «mouse», на котором основаны все плагины для взаимодействий [с мышью]. Для достижения этого,
draggable
, droppable
и т. п. наследуются от плагина «mouse»:jQuery.widget( "ui.draggable", $.ui.mouse, {...} );
Если вы не указываете этот аргумент, виджет наследуется напрямую от «основного виджета» jQuery.Widget (обратите вниманию на разницу между
jQuery.widget
с маленькой w и jQuery.Widget
с большой W).Последний аргумент — литерал объекта, который будет использоваться в качестве прототипа для каждого экземпляра виджета. Фабрика создает цепочку прототипов, соединяя прототип виджета со всеми виджетами от которого он наследуется, вплоть до базового
jQuery.Widget
.При вызове jQuery.widget в прототипе jQuery (jQuery.fn) создается новый метод, соответствующий имени виджета, в нашем случае это будет
jQuery.fn.multi
. Метод .fn
служит интерфейсом между элементами DOM, получаемыми в объекте jQuery и экземплярами виджета. Экземпляр виджета создается для каждого элемента в объекте jQuery.Польза
Упрощенный подход, описанный в Гайдлайнах по разработке плагина, оставляет много вопросов с конкретной реализацией, когда дело касается структурированного кода и ООП ориентированных плагинах. Кроме того, они не предлагают никаких решений для общих задач. Фабрика виджетов предоставляет API jQuery UI, для связи с экземпляром плагина и решения нескольких повторяющихся задач.
- Создание неймспейса и прототипа
Кроме этого, для запросов и фильтрации генерируется псевдо-селектор на основе неймспейса и имени, например$(":demo-multi")
. - Связь между прототипом и
jQuery.fn
Реализуется черезjQuery.widget.bridge
- Слияние пользовательских настроек с настройками по умолчанию
Настройки по умолчанию могут быть изменены (прим. переводчика — видимо имеются ввиду настройки по умолчанию для всех экземпляров виджета — можно изменить дефолтные настройки в прототипе своего виджета в конфиге для конкретной страницы, например). - Экземпляр плагина доступен через
$("#something").data("pluginname")
Ссылка на объект jQuery, содержащий DOM элемент (прим. переводчика — имеется ввиду ссылка на конкретный DOM элемент, на котором в данный момент отрабатывает экземпляр плагина) доступна в качестве свойства экземпляраthis.element
, поэтому становится очень легко взаимодействовать с плагином и элементом. - Методы виджета доступны через аргументы метода прототипа jQuery —
$("#something").multi("refresh")
— или напрямую через экземпляр —$("#something").data("multi").refresh()
. - Предотвращает появление нескольких экземпляров на одном элементе
Механизм коллбеков, на которые может подписываться пользователь:this._trigger("clear")
- Подписаться:
$( "#something" ).multi({ clear: function( event ) {} });
- Или, если через
.bind
:
$( "#something" ).bind( "multiclear", function( event ) {} );
(прим. переводчика — обратите внимание имя события склеилось из имени плагина и события)
- Подписаться:
- Механизм облегчающий изменение опций плагина после инициализации
- Просто включать/отключать или уничтожать экземпляр и возвращаться к изначальному состоянию. (прим. переводчика — смотрите дальше приватные методы
_destroy
и т. д.)
Создание собственного прототипа
Инфраструктура
Литерал объекта, передаваемый в качестве прототипа может быть устроен как угодно, но он обязательно должен содержать
options
(прим. переводчика — опции по умолчанию), коллбеки _create
, _setOption
, и destroy
.Пример
(function( $ ) {
$.widget( "demo.multi", {
// Эти опции будут использоваться в качестве опций по умолчанию
options: {
clear: null
},
// Создаем виджет
_create: function() {
},
// Используем метод _setOption для реагирования на изменения в настройках
_setOption: function( key, value ) {
switch( key ) {
case "clear":
// обрабатываем изменения
break;
}
// В jQuery UI 1.8, мы вручную вызываем метод _setOption в родительском виджете
$.Widget.prototype._setOption.apply( this, arguments );
// В jQuery UI 1.9 и выше, мы будем использовать метод _super
this._super( "_setOption", key, value );
},
// Используем метод destroy для очистки любых изменений в DOM, которые сделал наш виджет
destroy: function() {
// В jQuery UI 1.8 нужно вызывать метод destroy из родительского виджета
$.Widget.prototype.destroy.call( this );
// В jQuery UI 1.9 и выше вместо этого мы определим метод _destroy и не будем вызывать метод базового виджета
}
});
}( jQuery ) );
Инкапсуляция в методах
Ваш объект скорей всего будет содержать методы, для обработки различных специфичных операций, например построение и вставка новых элементов или обработка событий. Разумно было бы использовать изолированные методы для каждой операции, вместо обработки кучи всего в методе
_create
. Это позволит не дублировать код, в случае изменений.Например, в гипотетическом виджете, расширяющим
<select multiple>
, кто-то захочет пробежаться по дочерним <option>
, для создания соответствующих <li>
и <ul>
. Это можно реализовать через метод _create
:_create: function() {
var self = this;
this.list = $( "<ul>" ).insertAfter( this.element );
this.element.hide().find( "option" ).each(function( i, el ) {
var $el = $( el ),
text = $( el ).text(),
item = $( "<li class='multi-option-item'>" + text + "</li>" );
item.appendTo( self.list ).click(function(){
console.log( $el.val() );
});
});
}
К сожалению, если оставить так код в
_create
, это создаст сложности в определении связей между оригинальным элементом <option>
и пунктами списка который мы создаем, или проблему при изменении состояния элементов <option>
, которые были добавлены или удалены из изначального <select>
уже после инициализации виджета. Вместо этого мы сделаем метод refresh
, отвечающий за работу с элементом, и вызывающийся из метода _create
. Мы также вынесем отдельно обработку щелчков по элементам списка, и будем использовать делегирование события, что бы не навешивать новые обработчики, после создания нового пункта.Пример
_create: function() {
this.list = $( "<ul>" )
.insertAfter( this.element )
.delegate( "li.multi-option-item", "click", $.proxy( this._itemClick, this ) );
this.element.hide();
this.refresh();
},
refresh: function() {
// Отслеживаем сгенерированные пункты списка
this.items = this.items || $();
// Используем класс, что бы не затронуть уже созданные пункты
this.element.find( "option:not(.demo-multi-option)" ).each( $.proxy(function( i, el ) {
// Добавляем класс, что бы опция не обрабатывалась при следующем обновлении
var $el = $( el ).addClass( "demo-multi-option" ),
text = $el.text(),
// Создаем пункт списка
item = $( "<li class='multi-option-item'>" + text + "</li>" )
.data( "option.multi", el )
.appendTo( this.list );
// Сохраняем в кеш
this.items = this.items.add( item );
},this));
// Если опции уже нет в селекте, удаляем созданную для неё замену из списка и кеша
this.items = this.items.filter( $.proxy(function( i, item ) {
var isInOriginal = $.contains( this.element[0], $.data( item, "option.multi" ) );
if ( !isInOriginal ) {
$( item ).remove();
}
return isInOriginal;
}, this ));
},
_itemClick: function( event ) {
console.log( $( event.target ).val() );
}
Приватные v. Публичные методы
Как вы наверное заметили, некоторые методы мы написали с нижним подчеркиванием вначале, в то время как некоторые без него. Методы с префиксом обрабатываются как «приватные». Фабрика блокирует все попытки вызвать их через
$.fn
$( "#something" ).multi( "_create" )
Код выше вызовет возбуждение исключения. Но, раз они присутствуют в прототипе виджета, они считаются приватными только по соглашению. При вызове через
.data()
можно вызвать любой из этих методов напрямую:$( "#something" ).data( "multi" )._create()
Как же принять правильное решение? Если пользователям ваших виджетов понадобятся определенные методы, то сделайте их публичными. Пример с
refresh
показателен: раз пользователь может управлять элементами селекта, мы должны обеспечить ему возможность обновить его. С другой стороны, служебная функция для обработки событий, как например _itemClick
, требуется только для внутреннего использования, и совсем не нужна в публичном интерфейсе плагина.Свойства
this.element
Элемент, используемый экземпляром плагина. Например:
$( "#foo" ).myWidget()
В этом случае
this.element
будет объектом jQuery, содержащим элемент с id foo
. Для нескольких элементов, для которых вызывается .myWidget()
, отдельный экземпляр плагина будет вызван для каждого элемента. Другими словами this.element
будет всегда содержать только один элемент.this.options
Опции, используемые для настройки плагина. При создании экземпляра, любые опции, переданные пользователем, автоматически объединяются с заданными в
$.demo.multi.prototype.options
. Пользовательские опции перезаписывают опции по умолчанию.this.namespace
Пространство имен плагина, в нашем случае «demo». Как правило не используется в плагинах.
this.name
Имя плагина, в нашем случае «multi». Немного полезней чем
this.namespace
, но в большинстве случаев также не используется.this.widgetEventPrefix
Свойство, использующееся для именования событий плагина. Например,
dialog
имеет коллбек close
, при его исполнении всплывет событие dialogclose
. Имя события состоит из префикса события и имени коллбека. По умолчанию значение widgetEventPrefix
такое же как и имя виджета, но может быть перезаписано. Например, если пользователь начал перетаскивать элемент, мы не хотим что бы всплывало событие draggablestart
, мы хотим что бы оно называлось dragstart
, для этого мы делаем префикс события равным «drag». Если имя коллбека совпадает с префиксом события, то событие будет без префикса. Это позволит избежать событий наподобие dragdrag
.this.widgetBaseClass
Полезно для создания имен классов элементов виджета. Например, если нужно пометить элемент как активный
element.addClass( this.widgetBaseClass + "-active" )
Для большинства плагинов это не обязательно, потому что проще написать что-то вроде
.addClass( "demo-multi-active" )
. Приведенный пример актуальнее для самой фабрики и абстрактных плагинов, таких как $.ui.mouse
.Методы
(прим. переводчика - лучше всего, конечно, ознакомиться напрямую с документацией)
_create
Метод, в котором настраивается всё, относящееся к вашему виджету — создание элементов, навешивание событий и т. д. Метод вызывается один раз, сразу после создания экземпляра.
_init
Метод, вызывающийся каждый раз при вызове виджета, вне зависимости от количества передаваемых аргументов. Во время первого вызова
_init
вызывается после _create
. Он также может быть вызван в любое время после создания виджета, в этом случае _init
может служить для ре-инициализации, без выполнения уничтожения и повторного создания.destroy
Метод, уничтожающий экземпляр плагина и проводящий другие необходимые вам действия. Все модификации, которые делает ваш плагин должны уничтожаться методом
destroy
. Включая удаление классов, событий, уничтожение создаваемых элементов, и т. д. Это начальная точка для уничтожения вашего плагина, но для каждого плагина она пишется индивидуально, исходя из ваших нужд.option
Используется для задания опций после создания экземпляра. Сигнатура метода похожа на методы
.css()
и .attr()
. Вы можете задать только имя, что бы получить значение, или имя вместе со значением, для того что установить его, либо передать объект, для установки нескольких значений. Метод вызывает _setOptions
, поэтому он не должен меняться сторонними плагинами._setOptions
Приватный метод, используется для установки настроек после создания экземпляра. Метод вызывает _setOption, поэтому он не должен меняться сторонними плагинами.
_setOption
Вызывается, когда пользователь меняет значение через метод
option
. Этот метод можно менять в своем плагине, если нужно особое поведение при изменении определенных опций. Например, если в диалоговом окне меняется значение заголовка окна, нужно запустить обновление заголовка._setOption: function(key, value) {
if (key === 'title') {
this.titleElement.text(value);
}
$.Widget.prototype._setOption.apply(this, arguments);
}
Вызывая родительский метод
_setOption
, мы устанавливаем новое значение опции. Это не должно выполняться _setOption
. Иногда требуется сравнить старое и новое значения, что бы определить корректное поведение. Вы можете сравнить this.options[key]
со значением, так как вызов родительского метода _setOption
происходит уже в самом конце. Если вам не нужно ничего сравнивать, вы можете делать вызов родительского _setOption
в самом начале метода.enable
Хелпер, вызывающий
option('disabled', false)
. Вы также можете отлавливать вызов этого хелпера, проверяя:if (key === "disabled")
в вашем
_setOption
.disable
Хелпер, вызывающий
option('disabled', true)
. Вы также можете отлавливать вызов этого хелпера, проверяя:if (key === "disabled")
в вашем _setOption.
_trigger
Этот метод необходимо использовать для всех коллбеков. Имя выполняемого коллбека единственный обязательный параметр. Все коллбеки также триггерят событие (см. описание
this.widgetEventPrefix
выше). Вы можете также передать объект события, запустившего коллбек. Например, событие drag
запускается событием mousemove, и оно должно передаваться в _trigger
. Третий параметр, объект с данными, которые передаются в обработчики коллбека и события. Данные, передаваемые в этом объекте должны относиться только к текущему событию, и не должны отдаваться другими методами плагина.