Використання фабрики віджетів jQuery UI
Ukrainian (українська мова) translation by AlexBioJS (you can also view the original English article)
Довгий час створення користувальницьких засобів керування в jQuery відбувалося тільки за допомогою розширення простору імен $.fn
(* додавання методу до jQuery.prototype. Тут і надалі примітка перекладача). Цей спосіб працює добре для створення простих віджетів (* у віконній системі - стандартний, повторно використовуваний графічний елемент інтерфейсу користувача (кнопка, повзунок, вікно редагування тощо). користувацькі засоби керування (user controls) в Apple Computers), проте якщо ви переходите до створення віджетів зі зберіганням стану (* вони мають життєвий цикл, в них підтримується стан і вони реагують на зміни. Для них необхідно написати багато коду для їх ініціалізації та керування станом; на відміну від них віджети без зберігання стану просто виконують певні дії, наприклад, якщо ви встановлюєте текст елемента за допомогою .text( "hello" ), то при цьому не було етапу налаштування і результат завжди той самий), то використовувати його вже стає обтяжливо. Для підтримки розробників при створенні віджетів команда jQuery UI розробила фабрику віджетів (Widget Factory), що полегшує процес їх розроблення.
Фабрика віджетів – частина jQuery UI Core – надає об'єктно-орієнтований спосіб реалізації стадій життєвого циклу віджета. У цьому циклі виділяють наступні стадії:
- створення та зруйнування віджета
- зміна опцій віджета
- використання викликів «super» для дочірніх віджетів
- оповіщення про події
Давайте ознайомимся з цим API на прикладі створення простого віджета кульової діаграми (* різновид гістограми, стовпчаста діаграма).
Віджет кульової діаграми
До того, як створити цей віджет, давайте розберемося з його складом. Кульова діаграма – різновид гістограми (* стовпчастої діаграми); поняття ввів Stephen Few (* Стівен Ф'ю).



Ця діаграма складається зі смужок та міток, що накладаються одна на одну, щоб відобразити відносну ефективність. На ній є шкала кількісних значень для відображення області значень. Завдяки такому сумісному використанню смужок та міток можна повідомити більше інформації без шкоди для читабельності. У легенді роз'яснюється, які дані яким кольором, штрихуванням чи видом лінії представлені.
Код HTML для цієї діаграми наводиться нижче:
<!-- Chart Container --> <div class="chart bullet-chart"> <!-- Legend --> <div class="legend" style=""> <div class="legend-item"> <span class="legend-symbol marker green"></span> <span class="legend-label">Green Line</span> </div> </div> <!-- Chart --> <div class="chart-container" style="width: 86%;"> <!-- Quantitative Scale --> <div class="tick-bar"> <div class="tick" style="left: 0%;"></div> <div class="tick-label" style="left: 0%;">0</div> <div class="tick" style="left: 25%;"></div> <div class="tick-label" style="left: 25%;">25</div> <div class="tick" style="left: 50%;"></div> <div class="tick-label" style="left: 50%;">50</div> <div class="tick" style="left: 75%;"></div> <div class="tick-label" style="left: 75%;">75</div> <div class="tick" style="left: 100%;"></div> <div class="tick-label" style="left: 100%;">100</div> </div> <!-- Bars --> <div class="bar" style="left: 0px; width: 75%;" bar-index="0"></div> <div class="bar blue" style="left: 0px; width: 50%;" bar-index="1"></div> <!-- Markers --> <div class="marker green" style="left: 80%;" marker-index="0"></div> <div class="marker red" style="left: 50%;" marker-index="1"></div> </div> </div>
Наш віджет, що у нас буде називатися jquery.bulletchart
, буде створювати цей HTML, використовуючи надані дані. З готовим віджетом можна ознайомитися на сторінці з демоверсією, яку ви можете скачати з GitHub. Виклик методу для створення віджета повинен буде виглядати наступним чином:
$('.chart').bulletchart({ size: 86, bars: [ { title: 'Projected Target', value: 75, css: '' }, { title: 'Actual Target', value: 50, css: 'blue' } ], markers: [ { title: 'Green Line', value: 80, css: 'green' }, { title: 'Minimum Threshold', value: 50, css: 'red' } ], ticks: [0, 25, 50, 75, 100] });
Всі значення задаються у процентах. Опцією size
можна скористатися, коли ви хочете відобразити декілька кульових діаграм відносного розміру одна за одною. Опція ticks
використовується для нанесення відміток на шкалу. Мітки та смужки задаються у вигляді масиву літералів об'єктів із властивостями title
, value
та css
.
Створюємо віджет
Тепер, коли ми розібралися зі структурою віджета, давайте розпочнемо його створення. Віджет створюється за допомогою виклику $.widget()
, аргументами якої виступають ім'я віджета та об'єкт, у якому містяться методи його екземпляру. Точний API виглядає наступним чином:
jQuery.widget(name[, base], prototype)
Зараз ми будемо працювати тільки з першим та третім аргументами. Базова заглушка для нашого віджета виглядає наступним чином:
$.widget('nt.bulletchart', { options: {}, _create: function () {}, _destroy: function () {}, _setOption: function (key, value) {} });
Рекомендується, щоб у першому аргументі завжди містився простір імен перед іменем віджета. У нашому випадку ми використовуємо 'nt.bulletchart'. Всі віджети jQuery UI належать до простору імен 'ui'. Хоча ми й додаємо наш віджет до певного простору імен, у виклику, що ми використовуємо для створення віджета, для елемента простір імен відсутній. Тому, щоб створити кульову діаграму, ми будемо просто викликати $('#elem').bulletchart()
.
Властивості екземпляра задаються в об'єкті, що йде за іменем віджета. Умовно вважається, що всі приватні методи віджета повинні мати префікс '_'. У літералі об'єкта необхідно задати певні властивості: options
, _create
, _destroy
та _setOption
.
-
options
: це – опції за налаштуванням для віджета. -
_create
: фабрика виджетів викликає цей метод при першій ініціалізації віджета. Цей метод використовується для створення первісного DOM та приєднання яких-небудь обробників подій. -
_init
: після виклику_create
фабрика викликає_init
. Цей метод використовується для скидання віджета до вихідного стану. Тільки-но віджет створено, скинути віджет можна також за допомогою виклику конструктора без аргументів, наприклад, після виклику $.bulletchart() також відбудеться скидання віджета. У цій функції зсередини викликається_init
. -
_setOption
: викликається, коли ви встановлюєте опцію для віджета, наприклад:$('#elem').bulletchart('option', 'size', 100)
. Пізніше ми розглянемо інші способи задання опцій для віджета.
Створюємо первісний DOM за допомогою _create
Наш віджет реалізується в методі _create
. Саме тут ми створюємо базову структуру діаграми. З функцією _create
ви можете ознайомитися нижче. Ви помітите, що найважливіше, що тут відбувається, – це створення контейнера верхнього рівня. Фактична робота по створенню DOM для смужок, відміток та міток відбувається в методі _setOption
. Це може здатися не зовсім інтуїтивно-зрозумілим спочатку, проте для цього є поважна причина.
_create: function () { this.element.addClass('bullet-chart'); // chart container this._container = $('<div class="chart-container"></div>') .appendTo(this.element); this._setOptions({ 'size': this.options.size, 'ticks': this.options.ticks, 'bars': this.options.bars, 'markers': this.options.markers }); }
Зверніть увагу, що значення смужок, відміток та міток також можуть бути змінені шляхом задання опцій для віджета. Якщо би ми розмістили код для його створення всередині _create
, то ми би продублювали себе в _setOption
. Завдяки переміщенню коду до _setOption
та виклику його з _create
ми видаляємо дублювання і також централізуємо створення віджета.
Крім того в коді вище показано, як ще можна задавати значення опцій для віджета. За допомогою методу _setOptions
(зверніть увагу на множину) ви можете задати значення декількох опцій за один раз. Фабрика при цьому буде здійснювати зсередини окремі виклики _setOption
для кожної опції.
Метод _setOption
Для нашої програми метод _setOption
– робочий коник. Завдяки ньому здійснюється створення міток, смужок та відміток та зміна значень цих властивостей. При цьому відбувається видалення будь-яких елементів та їх відтворення на основі нового значення.
Метод _setOption
приймає в якості параметрів ключ опції та значення. Ключ – це ім'я опції, яке повинно відповідати одному з ключів опцій за налаштуванням. Наприклад, для того, щоб змінити смужки віджета ви могли би здійснити наступний виклик:
$('#elem').bulletchart('option', 'bars', [{ title: 'New Marker', value: 50 }])
Метод _setOption
для нашої кульової діаграми виглядає наступним чином:
_setOption: function (key, value) { var self = this, prev = this.options[key], fnMap = { 'bars': function () { createBars(value, self); }, 'markers': function () { createMarkers(value, self); }, 'ticks': function () { createTickBar(value, self); }, 'size': function () { self.element.find('.chart-container') .css('width', value + '%'); } }; // base this._super(key, value); if (key in fnMap) { fnMap[key](); // Fire event this._triggerOptionChanged(key, prev, value); } }
Тут ми створюємо простий набір імен опцій та їх відповідних функцій. При цьому ми працюємо тільки з дозволеними опціями та пропускаємо решту. Також тут відбувається ще два моменти: виклик _super()
та генерування події, що сповіщає про зміну опції. Ми розглянемо їх пізніше в цьому посібнику.
Для кожної опції, що змінює DOM, ми викликаємо певний допоміжний метод. Допоміжні методи: createBars
, createMarkers
та createTickBar
визначені ззовні від властивостей екземпляра віджета. Це так, оскільки вони однакові для всіх віджетів та повинні бути створені окремо для кожного екземпляру віджета.
// Creation functions function createTickBar(ticks, widget) { // Clear existing widget._container.find('.tick-bar').remove(); var tickBar = $('<div class="tick-bar"></div>'); $.each(ticks, function (idx, tick) { var t = $('<div class="tick"></div>') .css('left', tick + '%'); var tl = $('<div class="tick-label"></div>') .css('left', tick + '%') .text(tick); tickBar.append(t); tickBar.append(tl); }); widget._container.append(tickBar); } function createMarkers(markers, widget) { // Clear existing widget._container.find('.marker').remove(); $.each(markers, function (idx, m) { var marker = $('<div class="marker"></div>') .css({ left: m.value + '%' }) .addClass(m.css) .attr('marker-index', idx); widget._container.append(marker); }); } function createBars(bars, widget) { // Clear existing widget._container.find('.bar').remove(); $.each(bars, function (idx, bar) { var bar = $('<div class="bar"></div>') .css({ left: 0, width: '0%' }) .addClass(bar.css) .attr('bar-index', idx) .animate({ width: bar.value + '%' }); widget._container.append(bar); }); }
Значення всіх цих функцій задаються в процентах. Завдяки цьому гарантується, що діаграма змінюється належним чином при зміні розмірів елемента, в якому вона знаходиться.
Опції за налаштуванням
Якщо ми не вказуємо будь-які опції при створенні віджета, то у гру вступають опції за налаштуванням. За це відповідає властивість options
. Для нашої діаграми опції за налаштуванням виглядають наступним чином:
$.widget('nt.bulletchart', { options: { // percentage: 0 - 100 size: 100, // [{ title: 'Sample Bar', value: 75, css: '' }], bars: [], // [{ title: 'Sample Marker', value: 50, css: '' }], markers: [], // ticks -- percent values ticks: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100] }, ... }
Ми задаємо розмір (100%) та відмітки (кожні 10% діаграми), а смужки та мітки відсутні. На основі цих значень наша діаграма повинна виглядати наступним чином:



Поки що ми бачили, як створювати віджет за допомогою _create
та оновлювати його за допомогою _setOption
. Є ще один метод життєвого циклу, що буде викликатися при зруйнуванні віджета. Мова йде про метод _destroy
. При виклику $('#elem').bulletchart('destroy')
фабрика віджетів зсередини викликає _destroy
для вашого екземпляра віджета. Віджет відповідає за видалення всього, що було винесено їм до DOM. Це можуть бути класи та інші елементи DOM, що були додані у методі _create
. Також його можна використовувати для відв'язування яких-небудь обробників подій. Метод _destroy
повинен працювати протилежно методу _create
.
Для віджета нашої діаграми метод _destroy
доволі простий:
_destroy: function () { this.element.removeClass('bullet-chart'); this.element.empty(); },
Створення похідного класу, події та ще дечого
Ми реалізували майже всі можливості нашої діаграми: окрім останньої – легенди. Вона доволі важлива, оскільки підкаже, що означають мітки та смужки віджета. У цьому розділі ми додамо легенду поряд з діаграмою.
Замість того, щоб додати цю можливість напряму до віджета bulletchart, ми створимо підклас, bulletchart2
, в якому буде підтримка легенди. В процесі його створення ми також ознайомимося з деякими цікавими особливостями наслідування у фабриці віджетів.
Додавання легенди



У фабриці віджетів є підтримка наслідування віджетів для створення більш спеціалізованих версій. Раніше у посібнику ми бачили API для $.widget()
з трьома аргументами:
jQuery.widget(name[, base], prototype)
Завдяки цьому аргументу ми можемо вибрати базовий клас (* клас, із якого здійснюється спадкування підкласами) для нашого віджета. Наш віджет bulletchart2
, що є похідним від bulletchart
, буде мати наступну сигнатуру (* ім'я та список параметрів):
$.widget('nt.bulletchart2', $.nt.bulletchart, { options: { // Show/hide legend legend: true }, // this ensures we keep the same namespace as the base widgetEventPrefix: $.nt.bulletchart.prototype.widgetEventPrefix, _create: function () { ... }, _destroy:function(){ ... }, _setOption: function (key, value) { ... } })
Тут ватро звернути увагу на наступні моменти:
- Ми знову упереджаємо ім'я нашого віджета назвою простору імен:
nt.bulletchart2
. - Фабрика віджетів автоматично включає цей віджет до простору імен $.nt namespace. Тому для посилання на наш попередній віджет ми використовуємо
$.nt.bulletchart
. Подібним чином, якщо би ми створили похідний клас від стандартних віджетів jQuery UI, то ми би послалися на них за допомогою$.ui.widget-name
. - З властивістю
widgetEventPrefix
ми зустрічаємося вперше. Ми до неї ще повернемося, коли будемо обговорювати події. Решта властивостей екземпляра повинна бути вам вже добре знайома.
Оскільки цього разу ми додаємо більше значень елементів DOM (з урахування легенди), то нам необхідно буде переписати метод _create
. Це також означає, що нам необхідно буде переписати й метод _destroy
, щоб бути послідовними (* логічно закономірними).
_create: function () { var self = this; this._legend = $('<div class="legend"></div>') .appendTo(this.element); ... // Call the base this._super(); this._setOption('legend', this.options.legend); }, _destroy:function(){ this.element.find('.legend').empty(); ... this._super(); },
Тут ми маємо той же шаблон, що і в методі _create
раніше. Ми створюємо контейнер для легенди і потім викликаємо _setOption
для створення решти компонентів легенди. Оскільки ми перезаписуємо _create
, то нам необхідно впевнитися, що викликали _create
базового класу. Ми це робимо за допомогою _super
. Подібним чином ми викликаємо _super
для _destroy
.
Тепер ви можете задуматися: яким чином віджет знає, який саме метод _super викликати, якщо в ньому викликається простий невизначений _super
? Розгадка полягає в суті фабрики віджетів. При наслідуванні віджета фабрика встановлює різноманітні значення для _super
в залежності від функції екземпляра. Тому, коли ви викликаєте _super
із методу вашого екземпляра, то при цьому ви завжди посилаєтеся на коректний метод _super
.
Сповіщення про події
Оскільки у кульовій діаграмі є підтримка змін відміток та смужок, то легенда повинна бути синхронізована з цими змінами. Також ми реалізуємо переключення видимості відміток та смужок при клацанні елементів легенди. Це буде корисним, якщо у вас є декілька відміток та смужок. Ховаючи декілька елементів, ви побачите решту більш розбірливо.
Для реалізації синхронізації легенди зі змінами, що відбуваються з відмітками та смужками, віджет bulletchart2
повинен прослуховувати будь-які зміни, що відбуваються з цими властивостями. Базовий віджет bulletchart вже генерує спеціальну подію кожного разу, коли змінюються його опції. Нижче наведено відповідний фрагмент коду з базового віджета:
_setOption: function (key, value) { var self = this, prev = this.options[key]; ... // base this._super(key, value); if (key in fnMap) { fnMap[key](); // Fire event this._triggerOptionChanged(key, prev, value); } }, _triggerOptionChanged: function (optionKey, previousValue, currentValue) { this._trigger('setOption', {type: 'setOption'}, { option: optionKey, previous: previousValue, current: currentValue }); }
Кожного разу при заданні опції генерується подія setOption
. У цих подіях містяться попереднє та наступне значення для опції, яку було змінено.
Завдяки прослуховуванню цієї події у дочірньому віджеті ви зможете визначати, коли відбуваються зміни з відмітками та смужками. Віджет bulletchart2
підписується на цю подію у своєму методі _create
. Підписування на події, що генеруються віджетом, здійснюються за допомогою виклику this.element.on()
. this.element
вказує на елемент jQuery, для якого було ініційовано віджет. Оскільки подію буде згенеровано при виконанні дій із ним, підписування на подію необхідно виконати для нього.
_create: function () { var self = this; this._legend = $('<div class="legend"></div>') .appendTo(this.element); ... // Apply legend on changes to markers and bars this.element.on('bulletchart:setoption', function (event, data) { if (data.option === 'markers') { createLegend(data.current, self.options.bars, self); } else if (data.option === 'bars') { createLegend(self.options.markers, data.current, self); } }); // Call the base this._super(); this._setOption('legend', this.options.legend); }
Зверніть увагу на ім'я події, на яку ми підписуємося: 'bulletchart:setoption'
. Згідно з правилами, фабрика віджетів приєднує спеціальний префікс для подій, згенерованих при виконанні дій з віджетом. За налаштуванням цим префіксом є ім'я віджета, проте його можна легко замінити за допомогою властивості widgetEventPrefix
. У базовому віджеті відбувається його заміна на 'bulletchart:'
.
$.widget('nt.bulletchart', { options: { ... }, widgetEventPrefix: 'bulletchart:' ... });
Також нам необхідно підписатися на події 'click'
, що генеруються при виконанні дій з елементами легенди, для приховання/показу відповідної відмітки/смужки. Ми виконаємо це за допомогою методу _on
. У якості параметра для обробника в цьому методі використовується event (* дані події). Контекст обробника (this
) коректно відповідає екземпляру віджета. Ще одна угода при використанні _on
полягає в тому, що фабрика віджетів автоматично відв'язує події при зруйнуванні віджета.
_create: function () { ... // Listen to clicks on the legend-items this._on({ 'click .legend-item': function (event) { var elt = $(event.currentTarget), item = elt.data('chart-item'), selector = '[' + item.type + '-index=' + item.index + ']'; this.element.find(selector).fadeToggle(); elt.toggleClass('fade'); } }); ... }
Додаткові поради
Також у фабриці віджетів є ще декілька чудових особливостей, про які вам варто знати.
Посилаємося на екземпляр віджета.
До цього часу ми бачили тільки один спосіб виклику методів віджета. Ми виконували його за допомогою $('#elem).bulletchart('method-name')
. Але при цьому ми можемо викликати тільки публічні методи, наприклад: 'option', 'destroy', 'on', 'off'. Якщо ви хочете викликати ці методи напряму для екземпляра віджета, то є один спосіб. Фабрика віджетів приєднує екземпляр віджета до об'єкта data()
елемента. Ви можете отримати цей екземпляр наступним чином:
var widget = $('#elem').data('bulletchart'); widget.destroy();
Окрім цього, якщо ви хочете отримати набір усіх віджетів кульової діаграми на сторінці, то для цього також є селектор:
var allCharts = $(':nt-bulletchart');
Деякі спеціальні методи
Є декілька спеціальних методів, про які вам варто знати (вони використовуються набагато рідше): _getCreateEventData()
та _getCreateOptions()
. Перший використовується для приєднання даних події для події 'create', що генерується після завершення виклику _create
.
_getCreateOptions
використовується для приєднання додаткових опцій за налаштуванням для віджета або перезаписування вже існуючих. Опції, задані користувачем, перезаписують опції, що повернулися цим методом, а останні перезаписують опції віджета за налаштуванням.
Резюме
Це тільки верхівка айсберга. Якщо ви хочете дізнатися більше, то посилань нижче буде цілком достатньо. Звісно ж, найкращим джерелом інформації завжди буде власне початковий код. Я би дуже радив вам ознайомитися з початковим кодом jquery.ui.widget на GitHub.