Фабрика виджетов _jQueryUI

Содержание

jQuery.widget()

Все jQuery UI плагины (плагины поведения и виджеты) реализованы с помощью одного удобного инструмента — фабрики виджетов jQuery UI. Она позволяет создавать гибкие, настраиваемые плагины, с едиными, интуитивно понятными api, включающими свойства, методы и события, связанные с работой плагина.

Что же это такое?

Фабрика виджетов представляет из себя метод глобального объекта jQuery — jQuery.widget(). Он принимает 2 или 3 аргумента:

$.widget(pluginname, [parent], implementation)

pluginname — задается строкой в формате "namespace.pluginname". Часть namespace (пространство имен) обязательна. Если не указать пространство имен, фабрика виджетов сделает это за вас. Часть pluginname станет непосредственным именем плагина и прототипа. Например, jQuery.widget("demo.multi", {...}) приведет к созданию следующих полей jQuery.demo, jQuery.demo.multi и jQuery.demo.multi.prototype, а так же jQuery.fn.multi (то есть, появится возможность вызывать метод multi для выбранных элементов)
parent — необязательный аргумент, в котором можно указать другой плагин. В этом случае, создаваемый плагин будет от него наследоваться.
implementation — реализация работы плагина. Задается с помощью js-объекта. Именно этот объект, становится прототипом для экземпляров создоваемого плагина (конечно, если задан параметр parent, объект будет предварительно подвергнут некоторым изменениям, связанным с наследованием). Подробности реализации этого параметра смотрите ниже.

Библиотека jQuery, как вы уже возможно знаете, тоже предоставляет средства для создания плагинов. Однако, по сути, они дают только одно готовое решение — работу с выбранными элементами, с возможностью продолжать цепочку вызовов. Если создавать плагины с помощью $.widget(), то в руках разработчика окажется гораздо большее число готовых решений. Например возможность наследования, стандартизированный подход работы со свойствами, методами и событиями плагина, и это далеко не все.

Работа с готовыми плагинами

Прототип плагина

(Описание прототипов в википедии)

Когда вы делаете плагин с помощью $.widget(), jQuery UI создает javascript-объект, содержащий все настройки по умолчанию и методы плагина. Этот объект называют прототипом плагина. При установке плагина на определенный элемент, делатся отдельная копия этого прототипа, которая будет хранить состояние плагина именно на этом элементе. Такую отдельно взятую копию прототипа называют экземпляром плагина.

  • Внутри всех методов плагина, соответствующий экземпляр будет доступен в переменной this.
  • Получить экземпляр плагина, закрепленный за определенным элементом (скажем с id = something), можно так $("#something").data("plaginName").
  • Получить элемент, за которым закреплен определенный экземпляр плагина тоже просто — он будет доступен в свойстве element экземпляра плагина. Если быть точным, в этом свойстве лежит не сам элемент, а объект jQuery с ним.

Настройки (свойства) плагина

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

  • Задать настройки при инициализации плагина: $("#something").plaginName({option1:val1, option2:val2 ...});
  • Узнать/изменить настройки после инициализации: $("#something").plaginName("option", optionName, [value]); (чтобы изменить настройку optionName нужно указать value, чтобы узнать ее текущее значение — параметр value следует пропустить).

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

Плагины, организованные с помощью $.widget() могут обладать как открытыми, так и защищенными методами, предназначенными исключительно для внутренних потребностей плагина. Пользовательский доступ (то есть только к открытым методам) осуществляется следующим образом: $("#something").plaginName("plaginMethod", methodParams ...). Его использование гарантирует, что вы не вызовете лишних методов, которые могут привести к неправильной работе плагина.

Если вам по какой то причине нужно получить доступ ко всем методам плагина, то это можно сделать так: $("#something").data("plaginName").plaginMethod(methodParams) (то есть непосредственно на экземпляре плагина).

События

В методах плагина событие может быть вызвано с помощью this._trigger("myEventType"). В этом случае, обработчик этого события можно будет привязать одним из двух способов:

$("#something").multi({myEventType:function(event){}});
$("#something").bind("plaginNameMyEventType", function(event){});

Прочее

  • На основе указанных имени плагина и пространства имен, организуется селектор вида :namespace-pluginname, с помощью которого можно находить элементы, на которых активирован плагин.
  • Работа плагина на элементе может быть приостановлена и возобновлена. Еще, можно уничтожить экземпляр плагина и возвратить элемент в его прежнее состояние.
  • Плагины защищены от многократной активации на одном и том же элементе.

Создание плагина

В последнем параметре функции $.widget() нужно задавать реализацию создаваемого плагина (методы и свойства). Задается она с помощью js-объекта, который должен содержать минимум четыре поля: список свойств со значениями по умолчанию (поле с имененем options), конструктор (поле _create), деструктор (поле destroy), а так же поле для функции изменяющей параметры плагина (_setOption). Таким образом, минимальная реализация плагина имеет следующий вид:

(function( $ ) {
  $.widget( "demo.multi", {
 
    // Здесь задается список настроек и их значений по умолчанию
    options: { 
      clear: null
    },
 
    // Функция, вызываемая при активации виджета на элементе
    _create: function() {
    },
 
    // Этот метод вызывается для изменения настроек плагина
    _setOption: function( key, value ) {
      switch( key ) {
        case "clear":
          // предпринимаем действия по изменению свойства clear
          break;
      }
 
      // В jQuery UI 1.8, можно делегировать задачи методу
      // _setOption родительскому плагину:
      $.Widget.prototype._setOption.apply( this, arguments );
 
      // А начиная с UI 1.9 для этого достаточно вызвать метод _super
      this._super( "_setOption", key, value );
    },
 
    // деструктор - метод, который будет вызван при удалении плагина с элемента.
    // Он нужен чтобы очистить элемент от всех модификаций, сделанных плагином
    // (удалить вспомогательные классы, атрибуты и элементы)
    destroy: function() {
      // В jQuery UI 1.8, можно делегировать задачу очищения элемента
      // родительскому плагину
      $.Widget.prototype.destroy.call( this );
      // In jQuery UI 1.9 and above, you would define _destroy instead of destroy and not call the base method
    }
  });
}( jQuery ) );

Далее, описывая правила создания собственных плагинов мы будем реализовывать отдельный полноценный плагин, в качестве примера. В jQuery UI есть плагин, превращающий выбранные элементы в диалоговые окна. Сделаем похожий плагин, но с одним отличием — наши диалоговые окна будут сворачиваемыми. Назовем его cDialog (collapsed dialog).

Наследование

Плагины, создаваемые с помощью $.widget() обладают очень полезным свойством — наследованием. Это позволяет делать плагины, на основе других, уже существующих плагинов. Для этого необходимо указать объект с родительским плагином во втором параметре метода $.widget().

Создаваемый нами плагин cDialog унаследуем от стандартного dialog. Это будет выглядеть следующим образом:

Как видите, наш плагин уже умеет делать тоже, что и его родитель — dialog.

Свойства (настройки)

Как уже было сказано, свойства плагина и их значения по умолчанию задаются в поле option в объекте с реализацией плагина:

(function( $ ) {
  $.widget( "demo.multi", {
    option:{
      option1: default_value_1,
      option2: default_value_2,
      ..
    },
 
    // дальнейшая реализация плагина
}( jQuery ) );

Если создаваемый плагин наследуется от другого плагина, то он автоматически будет обладать всеми настройками родителя. Однако, значения по умолчанию унаследованных свойств могут быть изменены.

Для нашего плагина cDialog, уберем возможность быть растягиваемым с помощью ползунка, которая включена в dialog по умолчанию. Кроме этого, внесем одно новое свойство isCollapsed, которое будет хранить текущее состояние окна — развернутое/свернутое:

Итак, мы задали поле для хранения состояния, но пока никак не связали его с самим состоянием. Это будет реализовано ниже, с помощью методов плагина.

Предопределенные свойства

Все экземпляры плагина, созданного с помощью $.widget(), содержат предопределенные свойства с базовой информацией. Эти свойства создаются и заполняются автоматически.

element:jQuery 
Это поле содержит DOM-элемент, за который отвечает данный экземпляр плагина. Точнее, element представляет из себя объект jQuery, отвечающий за этот DOM-элемент.
options:object 
В этом поле лежит javascript-объект со всеми текущими настройками плагина на данном экземпляре плагина.
namespace:string 
В этом поле лежит строковое значение пространства имен данного плагина.
name:string 
В этом поле лежит строковое значение имени данного плагина.
widgetEventPrefix:string 
Определяет префикс, который будет добавляться к имени события. Например, для плагина cDialog, мы ниже создадим событие с именем collapsed, происходящее при сворачивании окна. В итоге, чтобы вызвать это событие средствами jQuery (например методом trigger), а так же зарегистрировать обработчик этого события, нужно будет обращаться к событию по имени cDialogcollapsed (имя плагина + имя события). То есть префиксом по умолчанию является имя плагина, если нужно это изменить, следует указать необходимый префикс в свойстве widgetEventPrefix, в прототипе плагина.
widgetBaseClass:string 
Все официальные плагины jQuery UI используют это свойство при создании имен классов, для элементов, участвующих в работе этих плагинов. Например, класс для перемещаемого в данный момент элемента, будет иметь следующий вид: widgetBaseClass + "-dragging" (по умолчанию, widgetBaseClass образуется так: "namespace-pluginname"). Для единичных плагинов, работа с этим свойством вряд-ли может оказаться полезной. Но оно может пригодиться, при создании иерархии плагинов, в частности в абстрактных плагинах, таких как $.ui.mouse.

Создание собственных методов плагина

Чтобы задать новый метод плагина, нужно задать его отдельным полем объекта с реализацией плагина (последний параметр функции $.widget()):

(function( $ ) {
  $.widget( "demo.multi", {
    myMethodName: function(params){
      // содержимое метода
    },
 
    // дальнейшая реализация плагина
}( jQuery ) );

В итоге, созданный метод myMethodName можно будет вызывать внутри других методов плагина так: this.myMethodName(params), а вне плагина: $("#something").plaginName("myMethodName", params).

Как можно было уже заметить, некоторые методы начинаются с символа нижнего подчеркивания. Такие методы являются скрытыми от внешних вызовов, то есть метод _myMethodName нельзя будет вызвать так: $("#something").plaginName("_myMethodName"). Скрытие методов очень удобно, когда некоторые из них не должны вызываться пользователями плагина, а нужны лишь для организации его внутренней функциональности. Например это может быть метод, проводящий подготовительные работы в DOM (добавление вспомогательных элементов, классов, атрибутов), при активации плагина на элементе странице. Однако, технически, закрытые методы все же могут быть вызваны из вне — обращением непосредственно к экземпляру плагина: $("#something").data( "plaginName" )._myMethodName().


В конструктор и деструктор создаваемого нами cDialog добавим реализацию с требуемыми изменениями в DOM (добавление кнопки сворачивания/разворачивания диалогового окна в конструктор и ее удаление в деструктор). Элемент с кнопкой, сворачивающей/разворачивающей окно, сделаем по принципу кнопки закрывающей диалоговое окно (чтобы посмотреть как устроена последняя, можно, например, использовать "просмотр кода элемента" в браузере chrome). Чтобы новую кнопку можно было легко находить на странице, а так же указывать css-правила для нее, присвоим элементу, который ее представляет класс ui-dialog-titlebar-hideshow (остальные используемые классы относятся к работе jQuery UI).

$.widget("my.cDialog", $.ui.dialog, {
  ...
  // конструктор плагина
  _create: function() {
    // вызовем в нем конструктор родительского плагина, который выполнит манипуляции с DOM
    // сделая из выбранного элемента диалоговое окно
    $.ui.dialog.prototype._create.call(this);
 
    // найдем элемент, оборачивающий все диалоговое окно
    var $o = this.element.parent();
    // в диалоговое окно добавим элемент с кнопкой сворачивания/разворачивания
    $('<a class="ui-dialog-titlebar-hideshow ui-corner-all" href="#">'+
      '<span class="ui-icon ui-icon-circle-triangle-s"></span></a>')
    .insertAfter($o.find('.ui-dialog-title'));
  },
 
  // деструктор плагина
  destroy: function() {
    // удалим из диалогового окна кнопку сворачивания/разворачивания
    this.element.parent().find('.ui-dialog-titlebar-hideshow').remove();
 
    // вызовем в деструктор родительского плагина, который удалит
    // оставшиеся следы диалогового окна 
    $.ui.dialog.prototype.destroy.call(this);
  },
  ...
}

Кроме этого, организуем методы collapse и uncollapse, для сворачивания и разворачивания диалогового окна соответственно. Свернутому элементу диалогового окна будем присваивать класс ui-dialog-hideshow-close:

$.widget("my.cDialog", $.ui.dialog, {
  ...
      // функция сворачивающая окно
      collapse: function(){
        // найдем элемент с кнопкой, открывающей/закрывающей окно
        var $clButt = this.element.siblings('.ui-widget-header').find('.ui-dialog-titlebar-hideshow');
        // проверим, не закрыто ли уже окно
        if(this.element.hasClass('ui-dialog-hideshow-close')) return;
        else this.element.addClass('ui-dialog-hideshow-close');
        // скроем содержимое и ручку растягивания окна (если она есть)
        this.element.hide().siblings('.ui-resizable-handle').hide();
        // изменим иконку закрытия окна на обратную
        $clButt.find('span').toggleClass('ui-icon-circle-triangle-s ui-icon-circle-triangle-n');
        this.isCollapsed = true;
      },
 
      // функция разворачивающая окно
      uncollapse: function(){
        // найдем элемент с кнопкой, открывающей/закрывающей окно
        var $clButt = this.element.siblings('.ui-widget-header').find('.ui-dialog-titlebar-hideshow');
        // проверим, не открыто ли уже окно
        if(!$clButt.hasClass('ui-dialog-hideshow-close')) return;
        else this.element.removeClass('ui-dialog-hideshow-close');
        // откроем содержимое и ручку растягивания окна (если она есть)
        this.element.show().siblings('.ui-resizable-handle').show();
        // изменим иконку открытия окна на обратную
        $clButt.find('span').toggleClass('ui-icon-circle-triangle-s ui-icon-circle-triangle-n');
        this.isCollapsed = false;
      },
  ...
}

Добавим реализацию метода _setOption, в котором обработаем изменение добавленного нами ранее свойства isCollapsed и получим следующий результат:


Предопределенные методы

Все экземпляры плагина, созданного с помощью $.widget(), содержат предопределенные методы (их список представлен ниже). Методы _create, _init, destroy и _setOption можно переписывать ("перекрывать") реализуя требуемую функциональность (например _create переписывают для обработки активации плагинов, _setOption для обработки изменения свойств). Реализацию остальных методов переписывать не следует, их следует только использовать.

_create([properties]) 
Это так называемый конструктор — метод, который будет автоматически вызываться при инициализации плагина на элементе страницы. В нем следует реализовать все подготовительные действия для дальнейшей работы плагина на элементе (добавить вспомогательные элементы, атрибуты, классы, прикрепить обработчики событий).
_init([properties]) 
Этот метод вызывается в случаях инициализации (установке плагина на элемент) и переинициализации, когда происходит попытка инициализации плагина на элементе, где уже установлен этот плагин. Последнее важно, чтобы задать поведение плагина при попытке переинициализации — нужно ли сбрасывать старые настройки и заменять их на новые или стоит игнорировать попытку повторной установки плагина на элемент. Технически, метод _init вызывается, когда на элементе вызывается основной метод плагина (jQueryElement.plaginName()), без аргументов или с одним аргументом — настройками плагина. При первом вызове плагина (т.е. его инициализации) _init будет вызван сразу после метода _create.
destroy() 
Так называемый деструктор. Этот метод вызывается при удалении плагина с элемента. В нем стоит организовывать очищение элемента от следов плагина — удалить вспомогательные элементы, атрибуты, классы, прикрепленные обработчики событий, связанные с работой плагина.
option(key, value) 
Этот метод используется для получения и изменения значений свойств (настроек) плагина. Имеет такую же сигнатуру как и методы jQuery .css() или .attr(). Переписывать реализацию option не стоит. Если необходимо обработать какие-либо параметры особым образом, то следует делать это в _setOption.
_setOptions(valueMap) 
Метод, изменяющий сразу группу настроек. Переписывать его реализацию не стоит.
_setOption(key, value) 
Метод, автоматически вызываемый при попытке изменить свойство плагина с помощью методов option или _setOptions. В случаях, когда необходимо определенным образом реагировать на изменение того или иного параметра, следует прописывать эти реакции в данном методе. Например, когда изменяется свойство, отвечающее за содержимое заголовка диалогового окна, следует обновить содержимое соответствующего элемента:
_setOption: function(key, value) {
  // отдельно обработаем изменение параметра, если это title
  if (key === 'title') {
    this.titleElement.text(value);
  }
 
  // далее вызываем стандартные действия по изменению свойств
  $.Widget.prototype._setOption.apply(this, arguments);
}

Как видно в данном примере, в _setOption удобно прописывать необходимые реакции, на изменения определенных свойств, после чего вызывать базовый _setOption, который произведет непосредственное изменения требуемого свойства. Если внутри _setOption будет необходимо узнать текущее значение какого-либо свойства, можно будет узнать его с помощью this.options[key].

enable() 
Возобновляет работу плагина на элементе.
disable() 
Остонавливает работу плагина на элементе.
_trigger(name, [eventObject], [data]) 
С помощью этого метода можно вызывать собственные события плагина. Первый параметр, который необходимо передавать в метод это тип события (строково значение с его именем). Отметим, что префикс при этом указывать не нужно (см. описания свойства widgetEventPrefix). Если данное событие вызывается в ответ на выполнение одного из стандартных событий javascript, то вторым параметром в _trigger, следует передать получаемый объект event. Все остальные данные, которые вы хотели бы передать в обработчик вызываемого с помощью _trigger события, следует указывать в третьем параметре data.

События

Используя метод _trigger() (см. его описание выше), можно генерировать собственные события плагина. Например, организуем для нашего плагина cDialog события collapse и uncollapse, чтобы пользователи нашего плагина могли обрабатывать сворачивание и разворачивание диалоговых окон. Для этого, в методах collapse и uncollapse нужно будет вызывать соответствующие события:

$.widget("my.cDialog", $.ui.dialog, {
  ...
      // функция сворачивающая окно
      collapse: function(){
        // найдем элемент с кнопкой, открывающей/закрывающей окно
        var $clButt = this.element.siblings('.ui-widget-header').find('.ui-dialog-titlebar-hideshow');
        // проверим, не закрыто ли уже окно
        if(this.element.hasClass('ui-dialog-hideshow-close')) return;
        else this.element.addClass('ui-dialog-hideshow-close');
        // скроем содержимое и ручку растягивания окна (если она есть)
        this.element.hide().siblings('.ui-resizable-handle').hide();
        // изменим иконку закрытия окна на обратную
        $clButt.find('span').toggleClass('ui-icon-circle-triangle-s ui-icon-circle-triangle-n');
        this.isCollapsed = true;
 
        // генерация события collapse
        this._trigger("collapse");
      },
 
      // функция разворачивающая окно
      uncollapse: function(){
        // найдем элемент с кнопкой, открывающей/закрывающей окно
        var $clButt = this.element.siblings('.ui-widget-header').find('.ui-dialog-titlebar-hideshow');
        // проверим, не открыто ли уже окно
        if(!$clButt.hasClass('ui-dialog-hideshow-close')) return;
        else this.element.removeClass('ui-dialog-hideshow-close');
        // откроем содержимое и ручку растягивания окна (если она есть)
        this.element.show().siblings('.ui-resizable-handle').show();
        // изменим иконку открытия окна на обратную
        $clButt.find('span').toggleClass('ui-icon-circle-triangle-s ui-icon-circle-triangle-n');
        this.isCollapsed = false;
 
        // генерация события uncollapse
        this._trigger("uncollapse");
      },
  ...
}

После этого, можно будет привязывать обработчики этих событий, обращаясь к самому плагину или средствами обработки событий jQuery:

$("#something").cDialog({collapse:function(event){обработка события} });
$("#something").bind("cDialogcollapse", function(event){обработка события});