Как стать автором
Обновить

jQuery UI как инфраструктура для плагинов

Время на прочтение6 мин
Количество просмотров21K

Введение


jQuery UI больше всего известен как набор готовых виджетов. Главное их преимущество, на мой взгляд, — консистентное API: каждый виджет управляется одинаково. Второе их преимущество — они хранят свое состояние: если повторно навесить виджет на элемент, то результатом будет уже существующий инстанс виджета.
Но jQuery UI — это не только набор окошечек и табов (далеко не всеми любимых). Это еще целая инфраструктура для создания своих виджетов: с удобным консистентным API, с хранением состояния и с возможностью наследования. Как ни странно, это для многих новость, в результате чего и появилась эта статья — так же, как это было новостью для меня всего несколько месяцев назад.


Лирическое отступление


Дабы не тратить время и место, везде ниже по коду подразумевается, что window.$ == window.jQuery, undefined никто не испортил, и что мы подключаем jQuery, только jQuery и ничего, кроме jQuery, и что все объявления обернуты в нечто вроде этого:
(function($) {
  // наш код
})(jQuery)

* This source code was highlighted with Source Code Highlighter.

Так же подразумевается, что читатель неплохо знаком с jQuery и хотя бы читал документацию от jQuery UI.

Магия $.widget


Вся магия заключается в методе $.widget. Он принимает 2 (или 3 — в случае наследования) параметра. Официально этот метод называется «фабрика виджетов».
Первый параметр — строка, она содержит неймспейс и собственно имя виджета, разделенные точкой. Например, "my.myWidget". Неймспейс обязателен; вложенность не поддерживается. Второй параметр — литерал объекта, который, собственно, и описывает наш виджет:
$.widget("my.myWidget", {
  options: {
    greetings: "Hello"
  },
_create: function() {
    this.element.html(this.options.greetings);
  }
})

* This source code was highlighted with Source Code Highlighter.

Функция, лежащая в поле под именем _create, служит конструктором, и будет вызвана при создании инстанса виджета; на этот инстанс и указывает this.
this.element — это элемент, на который был навешен виджет. Это всегда одиночный элемент, а не коллекция (как в случае обычных плагинов); если навешивать виджет на jQuery-объект, который содержит больше одного элемента, то будет создано столько инстансов, сколько элементов.
В поле options хранятся дефолтные настройки виджета. Это поле наследуется, так что оно всегда будет в виджете, даже если не объявлять его явно.
Если при вызове виджета передать объект, то переданный объект будет «смерджен» (с помощью метода $.merge) с дефолтными настройками еще до вызова _create.
За работу с настройками отвечает метод setOption:
$.widget("my.myWidget", {
  options: {
    greetings: "Hello"
  },
  _create: function() {
    this._render();
  },
  _render: function() {
    this.element.html(this.options.greetings);
  },
  setOption: function(key, value) {
    if (value != undefined) {
      this.options[key] = value;
      this._render();
      return this;
    }
    else {
      return this.options[key];
    }
  }
})

* This source code was highlighted with Source Code Highlighter.

Используется это так же, как в любом стандартном виджете:
var mw = $('.mywidget').myWidget({greeting: 'Hi there!'})
console.log(mw.myWidget('option', 'greeting')); // 'Hi there!'
mw.myWidget('option', 'greeting', 'O HAI CAN I HAZ CHEEZBURGER?');

* This source code was highlighted with Source Code Highlighter.


Приватные и публичные методы


К методу виджета можно обратиться примерно так же, как мы обращаемся к настройкам:
$.widget("my.myWidget", {
    options: {
        greetings: "Hello"
    },
    _create: function() {
        this._render();
    },
    _render: function() {
        this.element.html(this.options.greetings);
    },
    sayHello: function(saying) {
        alert(saying);
    },
    _setOption: function(key, value) {
        if (arguments.length == 1) {
            this.options[key] = value;
            this._render();
            return this;
        }
        else {
            return this.options[key];
        }
    }
})
// …
mw.myWidget("sayHello", 42);

* This source code was highlighted with Source Code Highlighter.

Но для этого этот метод должен быть публичным. Как сделать метод публичным в парадигме UI-ных плагинов? Это просто: публичными методами движок виджетов считает те, имена которых не начинаются с подчеркивания. Все остальные методы — приватные. Поля виджетов, не являющиеся функциями, всегда только приватные.
Это, конечно, не в полном смысле public и private методы, а их эмуляция, впрочем, достаточная для того, чтобы разграничить доступ.

Коллбэки


По сути, это просто шорткаты для привязки к пользовательским событиям внутри виджета. В виджет они передаются так же, как и настройки.
$.widget("my.myWidget", {
  options: {
    greetings: "Hello"
  },
  _create: function() {
    this._render();
  },
  _render: function() {
    this.element.html(this.options.greetings);
    this._trigger("onAfterRender", null, {theAnswer: 42})
  }
})
// …
var mw = $(".mywidget").myWidget(
  {
    greeting: "Hi there!",
    onAfterRender: function(evt, data) {
      console.log(data.theAnswer)
  }
})


* This source code was highlighted with Source Code Highlighter.

Это эквивалентно старому доброму .bind в таком виде:
mw.bind('onAfterRender.myWidget', function(evt, data) {console.log(data.theAnswer)})
* This source code was highlighted with Source Code Highlighter.


Деструкторы


Идущие «из коробки» виджеты имеют обыкновение генерировать кучу разметки. Хорошо это или плохо — вопрос дискусионный. Но, отчасти поэтому, отчасти потому, что ссылка на инстанс виджета записывается в expando-атрибут DOM-элемента — надо вызывать деструктор, когда вы уничтожаете виджет.
В качестве деструктора вызывается метод под названием destroy. К сожалению, его всегда надо вызывать явно. Чтобы было полное счастье, внутри деструктора должен быть следующий вызов:
$.Widget.prototype.destroy.call(this);
* This source code was highlighted with Source Code Highlighter.


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


Одна из самых вкусных вещей, хотя по ней практически нет информации.
Если вторым аргументом передать какой-то другой виджет A (наш виджет в этом случае идет третьим аргументом), новый виджет B будет его потомком.
Предположим, в нашем приложении — куча диалоговых окон, и все — модальные. При этом они не должны закрываться по Esc. Писать каждый раз вот такое совсем не хочется:
$('.dialog').dialog({
  modal: true,
  closeOnEscape: false,
  // … еще куча настроек, которые тоже могут быть
  // одинаковы для всех диалогов…
})

* This source code was highlighted with Source Code Highlighter.


Мы можем отнаследоваться от стандартного диалога и переопределить дефолтные настройки:
$.widget("my.mydlg", $.ui.dialog, {
  options: {
      modal: true,
     closeOnEscape: false,
  },
  _create: function() {
      $.ui.dialog.prototype._create.call(this);
  }
})

* This source code was highlighted with Source Code Highlighter.

Теперь заменяем во всем коде вызовы .dialog на .mydlg и наслаждаемся уменьшением дублирования. К сожалению, приходится явно указывать предка и вручную вызывать его конструктор.

Заключение


Мне кажется, что UI-ные виджеты — это неплохое средство модуляризации кода. В небольших и средних проектах они сами себе могут обеспечить достаточную инфраструктуру приложения.
При этом не надо тащить весь, довольно увесистый, jQueryUI — достаточно компонента core.
Паттерн, который лежит в основе этого виджетного движка, создатели называют bridge (хотя, конечно, метод $.widget — это фабрика). Допилив метод $.widget напильником, можно получить виджеты, которые сами читают свои настройки из разметки, сами находят витальные для себя элементы, и автоматически организовываются в иерархическую структуру. Но это явно тема для отдельной статьи.
Теги:
Хабы:
Всего голосов 87: ↑86 и ↓1+85
Комментарии51

Публикации