Гнучке управління подіями в jQuery - плагін jquery.behavior

Привіт, Хабр!

Мене звуть В'ячеслав Гримальський, я працюю над конструктором посадкових сторінок platformalp.uk.

Я розповім про інструмент для роботи з подіями, який спочатку був частиною конструктора, але потім був винесений в окремий плагін для jQuery — jquery-behavior.

Плагін використовує функціонал jQuery, доповнивши його наступними можливостями:

  • Робота з окремими групами обробників подій. Для цього використовуються контролери подій.
  • ви Можете відключити всі обробники подій контролера разом, не перераховуючи їх.
  • Стеження за спрацьовуваннями обробників подій.
  • Можна дізнатися точну кількість викликів кожного з них.
  • Можна припиняти роботу окремих обробників подій, або всього контролера разом.
  • Можливість отримати повний перелік обробників подій конкретного елемента, обробників подій з певним простором імен або просто всіх обробників подій контролера.

Покажу відразу, про що йдеться:

// Створюємо контролер подій.
// Кожен контролер працює зі своєю групою подій, і не знає про інших контролерах.
var behavior = $.Behavior();

// Додаємо обробник подій. Синтаксис функцій такий же, як у jQuery.
behavior('body').click(function () {});
behavior(window).on('resize.demo', function () {});
behavior('.top').on('click.demo', '.btn', function () {});

// Призупиняє виконання обробників подій, відповідних фільтру.
behavior.pause({
types: '.demo'
});

// Відновлюємо виконання обробників подій, відповідних фільтру.
behavior.resume({
target: 'u',
types: 'click.demo'
});

// Відключаємо всі обробники подій, створені контролером.
behavior.off();

Контролери подій
var behavior = $.Behavior();

Контролер подій — об'єкт, який може додавати обробники подій, і який зберігає про них всю інформацію.

Він може видаляти і припиняти обробники подій, але тільки ті, що в ньому ж і були додані.

Це дозволяє розділяти обробники подій різних, незалежних, частин проекту, і працювати з ними окремо.

Робота з обгортками в стилі jQuery
Плагін дозволяє створювати обгортки об'єктів для роботи з ними.

jQuery:
$('body').click(function () {});
$(window).on('resize', function () {});

Плагін:
behavior('body').click(function () {});
behavior(window).on('resize', function () {});

Тут всі функції та їх синтаксис скопійовані з jQuery, повний їх список:
on one trigger triggerHandler off bind unbind delegate undelegate hover blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu

Ви можете просто взяти працює з подіями код на jQuery і замінити функцію "$" на «behavior», або як у вас буде називатися контролер подій, і все буде працювати.

jQuery:
$('body').click(function () {}).one('mousemove', function () {}).trigger('click');

Плагін:
behavior('body').click(function () {}).one('mousemove', function () {}).trigger('click');

При цьому всі функції, що створювані таким чином будуть закріплені за використовуваним контролером, і ви зможете працювати з ними, використовуючи всі інші функції контролера.

Додавання обробників подій — функції behavior.on() і behavior.one()
За аналогією з функціями jQuery, on додає обробник події, а one додає обробник події, який виконується лише один раз.

Синтаксис такий:

behavior.on({
// Стандартні параметри
target: 'body', // Елемент, на які вішаються обробник
types: 'click.namespace', // Назва або назви події з просторами імен
selector: '.btn', // Не обов'язково. Селектор для делегування подій
handler: function (event) {}, // Обробник події

// Додаткові параметри
throttle: { // Не обов'язково. Створює обгортку обробника функцією _.throttle
wait: 1000,
leading: true,
trailing: true
},
after: 1, // Не обов'язково. Створює обгортку обробника функцією _.after
log: true // Не обов'язково. Дозволяє "заглушити" логи подій, про яких пізніше.
});

Кілька прикладів.

behavior.on({
target: window,
types: 'resize',
handler: handlerFn
});
// аналогічно
behavior(window).on('resize', handlerFn);

behavior.on({
target: window,
types: 'resize',
handler: handlerFn,
throttle: {
wait: 200,
leading: false
}
});
// аналогічно
behavior(window).on('resize', _.throttle(handlerFn, 200, { leading: false }));

behavior.on({
target: 'body',
types: 'click',
selector: '.btn',
handler: handlerFn,
after: 2
});
// аналогічно
behavior('body').on('click', '.btn', _.after(2, handlerFn));

При використанні «повної» запису з параметрами after та throttle наявність бібліотеки underscore або lodash не обов'язково, оскільки ці функції вбудовані в плагін.

Відключення подій — функція behavior.off()
Ви можете відключити одним викликом все обробники подій, створені контролером.

behavior.off();

Ви можете відключити всі обробники подій певного елемента або групи елементів:

behavior.off({
target: window
});
// аналогічно
behavior(window).off();

behavior.off({
target: $btns
});
// аналогічно
behavior($btns).off();

behavior.off({
target: 'body .btn'
});
// аналогічно
behavior('body .btn').off();

Ви можете відключити всі обробники подій певного типу і простору імен:

behavior.off({
types: 'click'
});

behavior.off({
types: 'click.namespace'
});

behavior.off({
types: '.namespace1, .namespace2'
});

behavior.off({
types: 'click.namespace1, .namespace2'
});

Ви можете видаляти та делеговані події.

behavior.off({
target: 'body',
types: 'click',
selector: '.btn'
});
// аналогічно
behavior('body').off('click', '.btn');

behavior.off({
target: 'body',
types: '.namespace',
selector: '**'
});
// аналогічно
behavior('body').off('.namespace', '**');

А так само видалити всі обробники по посиланню.

behavior.off({
handler: handlerFunction
});

І звичайно, все це ви можете комбінувати.

behavior.off({
target: 'body',
types: '.namespace',
handler: handlerFunction1
});
// аналогічно
behavior('body').off('.namespace', handlerFunction1);

behavior.off({
target: 'body',
handler: handlerFunction1
});

Призупинення та відновлення роботи обробників — функції behavior.pause() і behavior.resume()
Аргументи такі ж, як і у функції behavior.off().

Призупинити одним викликом все обробники подій, створені контролером.

behavior.pause();

І повернути їх на роботу:

behavior.resume();

І далі всі приклади — за аналогією з behavior.off().

behavior.pause({
target: window
});

behavior.pause({
types: 'click.namespace1, .namespace2'
});

behavior.pause({
target: 'body',
types: 'click',
selector: '.btn'
});

behavior.pause({
target: 'body',
types: '.namespace',
handler: handlerFunction1
});

Призупинення та відновлення роботи контролера — функції behavior.start() і behavior.stop()
Ці функції схожі на behavior.pause() і behavior.resume(), але мають деякі відмінності.

Вони працюють не на рівні конкретних обробників подій, а на рівні контролера, тобто зупиняють не обробники подій, а сам контролер. І оскільки зупиняється весь контролер, то обробники подій, додані після виконання behavior.stop() не будуть працювати до тих пір, коли ми не відновимо роботу контролера функцією behavior.start().

При створенні контролера так само викликається behavior.start(), а при його руйнуванні — behavior.stop().

Події контролера
При створенні контролера можна вказати параметри onStart, onStop і onFire.

var behavior = $.Behavior({
onStart: function (data) {},
onStop: function (data) {},
onFire: function (event) {}
});

Функція onFire, викликається при кожному спрацьовуванні будь-якого з обробників подій контролера. Аргументи і контекст отримує ті ж, що і обробник події.

Функція onStart виконується при виклику behavior.start(). Може приймати перший аргумент.

Функція onStop, що логічно, виконується при виклику behavior.stop(), і так само може приймати його перший аргумент.

Щоб створюваний контролер подій спочатку був вимкнений, onStop не викликався, а події не виконувалися, поки ви не викличете behavior.start(), потрібно при створенні контролера вказати прапор active зі значенням false:

var behavior = $.Behavior({
active: false
});

Руйнування контролера — behavior.destroy()
Якщо контролер більше не потрібен, події потрібно вимкнути, і очистити всю пам'ять, варто скористатися цією функцією.

Використання декількох контролерів
Ви можете використовувати один контролер на весь проект, а можете ефективно ділити події на різні контролери.

Хороший приклад — реалізація drag'n'drop з допомогою плагіна.

Один контролер — події «сплячого» стану, інший — події перетягування, і ми між ними просто переключається.



Демо на JSFiddle: http://jsfiddle.net/fm22ptxv/
Великі червоні пікселі перетягуються мишкою.

Інструменти для налагодження
У плагін вбудовані інструменти для налагодження, дозволяють зрозуміти, що відбувається з обробниками. Які з них досі активні, які викликаються, а які не діють, і скільки їх взагалі «в живих» залишилося.

Отримання інформації про контролер — behavior.data()
Повертає всю необхідну інформацію про контролер — поточний стан, список обробників подій.

{
log: false,
namespace: "bhvr",
onFire: null,
onStart: null,
onStop: null,
records: Array
}

Важливо зрозуміти, що з себе представляють об'єкти з масиву records.

Припустимо, у нас є 2 кнопки з класами .btn1 і .btn2, і ми хочемо призначити їм по 2 однакових обробника події:

behavior('.btn1, .btn2').on('mousedown mouseup', handler);

Насправді буде додано 4 «низькорівневих» обробника подій, як якщо б ми писали так:

behavior('.btn1').on('mousedown', handler);
behavior('.btn1').on('mouseup', handler);
behavior('.btn2').on('mousedown', handler);
behavior('.btn2').on('mouseup', handler);

Так от, поле records буде містити інформацію про обробниках в такому вигляді, як ми їх оголошуємо.

У нашому прикладі це:

{
targets: JQuery[], // містить $('.btn1, .btn2')
types: 'mousedown mouseup', // назви подій і простору імен
handler: function (event) {}, // обробник події
selector: undefined, // селектор для делегування подій
calls: 0, // кількість викликів обробника handler
bindings: Array // ті самі "низькорівневі обробники"
}

А вже в bindings з records буде 4 подібних об'єкта:

{
target: JQuery[], // містить $('.btn1')
type: 'mouseup', // назва події
namespaces: string[], // список імен
handler: function (event) {}, // обробник події
selector: undefined, // селектор для делегування подій
calls: 0, // кількість викликів обробника handler
paused: false // стан обробника
}

Кількість викликів (calls) об'єкта в records завжди буде дорівнювати сумі кількостей дзвінків усіх його bindings.

А в bindings вони різні, і вважаються окремо, що зручно, коли потрібно дізнатися, які конкретно обробники подій викликаються, і на яких елементах вони спрацьовують.

Отримання списку обробників подій — функція behavior.filter()
Виконує пошук по всіх «низькорівневим» обробників, і повертає їх список.

Синтаксис у функції такої ж, як і у behavior.off(), behavior.pause() і behavior.resume().

Одержання повного списку обробників.

behavior.filter();

Отримання обробників подій об'єкта:

behavior.filter({
target: window
});

І так далі.

behavior.filter({
types: 'click.namespace1, .namespace2'
});

behavior.filter({
target: 'body',
types: 'click',
selector: '.btn'
});

behavior.filter({
target: 'body',
types: '.namespace',
handler: handlerFunction1
});

Логи подій
Іноді буває зручно відслідковувати, яка подія коли виконується.

Контролер має вбудований механізм логів, який виводить на консоль інформацію про кожному обробник подій, а так само про зупинення та продовження роботи самого контролера (функції behavior.stop() і behavior.start()).

Щоб контролер подій виводив в консоль інформацію про все, що відбувається, досить при створенні контролера вказати прапор log зі значенням true:

var behavior = $.Behavior({
log: true
});

Так само ви можете включити логи вже після створення контролера:

behavior.logOn();

Вимкнути логи можна так:

behavior.logOff();

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

Для цього при створенні контролера потрібно вказати функцію logFn:

var behavior = $.Behavior({
logFn: function (type, behavior, event data) {}
});

Аргумент type містить тип лода — start, stop або fire. Аргумент behavior містить посилання на сам контролер. Далі йдуть ті аргументи, які передаються в обробник події, це event і data. Аргумент data може бути відсутнім.

Тестування
Для того, щоб протестувати роботу плагіна, були взяті рідні тести подій jQuery, і трохи адаптовані під себе.

При цьому в тестах були виявлені кілька обробників подій, які не видалялися, хоча за цим в тестах намагаються стежити. Комміт з виправленням цього був прийнятий в jQuery.

Так само були написані тести для функцій, і ситуацій, який не зачіпаються рідними тестами.

Висновок
Перша версія плагіна була написана близько півтора року тому. З тих пір плагін багато разів переписувався і допрацьовувався, поки не прийняв свій поточний вигляд.

Весь цей час він активно використовується мною в розробці, і він насправді дуже зручний.

Буду радий будь-яким коментарям, зауважень і пропозицій.

Посилання на гитхаб: https://github.com/grimalschi/jquery-behavior

Велике спасибі!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.