Matreshka.js 2: события

  • Tutorial

image


Документация на русском
Github репозиторий


Всем привет!


Функциональность событий в Matreshka.js стала настолько богатой, что она, без сомнения, заслужила отдельной статьи.


Эта статья на английском


Основы: произвольные события


Начнем с самого простого. события во фреймворке добавляются методом on.


const handler = () => {
  alert('"someeevent" is fired');
};
this.on('someevent', handler);

В который можно передать список событий, разделенных пробелами.


this.on('someevent1 someevent2', handler);

Для объявления обработчика события в произвольном объекта (являющегося или не являющегося экземпляром Matreshka), используется статичный метод Matreshka.on (разница только в том, что целевой объект — первый аргумент, а не this).


const object = {};
Matreshka.on(object, 'someevent', handler);

События можно генерировать методом trigger.


this.trigger('someevent');

Для произвольных объектов можно воспользоваться статичным аналогом метода.


Matreshka.trigger(object, 'someevent');

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


this.on('someevent', (a, b, c) => {
  alert([a, b, c]); // 1,2,3
});
this.trigger('someevent', 1, 2, 3);

Или


Matreshka.on(object, 'someevent', (a, b, c) => {
  alert([a, b, c]); // 1, 2, 3
});
Matreshka.trigger(object, 'someevent', 1, 2, 3);

Здесь вы можете углядеть синтаксис Backbone. Всё верно: первые строки кода Matreshka.js писались под впечатлением от Backbone (даже код изначально был позаимствован оттуда, хотя и перетерпел большие изменения в дальнейшем).


Дальше, в этом посте, буду приводить вариант методов, использующих ключевое слово this (за исключением примеров делегированных событий). Просто помните, что on, once, onDebounce, trigger, set, bindNode и прочие методы Matreshka.js имеют статичные аналоги, принимаемые произвольный целевой объект в качестве первого аргумента.


Кроме метода on, есть еще два: once и onDebounce. Первый навешивает обработчик, который может быть вызван только однажды.


this.once('someevent', () => {
    alert('yep');
});
this.trigger('someevent'); // yep
this.trigger('someevent'); // nothing

Второй "устраняет дребезжание" обработчика. Когда срабатывает событие, запускается таймер с заданной программистом задержкой. Если по истечению таймера не вызвано событие с таким же именем, запускается обработчик. Если событие сработало перед окончанием задержки, таймер обновляется и снова ждет. Это реализация очень популярного микропаттерна debounce, о котором можно прочесть на Хабре, на англоязычном ресурсе.


this.onDebounce('someevent', () => {
  alert('yep');
});
for(let i = 0; i < 1000; i++) {
  this.trigger('someevent');
}
// через минимальный промежуток времени один раз покажет 'yep'

Не забывайте, что метод может принимать задержку.


this.onDebounce('someevent', handler, 1000);

События изменения свойства


Когда свойство меняется, Matreshka.js генерирует обытие change:KEY.


this.on('change:x', () => {
    alert('x is changed');
});
this.x = 42;

В случае, если вы хотите передать какую-нибудь информацию в обработчик события или же изменить значение свойства не вызывая при этом события "change:KEY", вместо обычного присваивания воспользуйтесь методом Matreshka#set (или статичным методом Matreshka.set), принимающим три аргумента: ключ, значение и объект с данными или флагами.


this.on('change:x', evt => {
    alert(evt.someData);
});
this.set('x', 42, { someData: 'foo' });

А вот как можно изменить свойство, не вызывая обработчик события:


this.set('x', 9000, { silent: true }); // изменение не вызывает событие

Метод set поддерживает еще несколько флагов, описание которых заставило бы выйти за рамки темы статьи, поэтому, прошу обратиться к документации к методу.


События, генерирующиеся перед изменением свойства


В версии 1.1 появилась еще одно событие: "beforechange:KEY", генерирующееся перед изменением свойства. Событие может быть полезно в случаях, когда вы определяете событие "change:KEY" и хотите вызвать код, предшествующий этому событию.


this.on('beforechange:x', () => {
    alert('x will be changed in few microseconds');
});

В обработчик можно передать какие-нибудь данные или отменить генерацию события.


this.on('beforechange:x', evt => {
    alert(evt.someData);
});

this.set('x', 42, { someData: 'foo' });
this.set('x', 9000, { silent: true }); // изменение не генерирует событие

События удаления свойства


При удалении свойств методом remove, генерируются события delete:KEY и delete.


this.on('delete:x', () => {
    alert('x is deleted');
});

this.on('delete', evt => {
  alert(`${evt.key} is deleted`);
});

this.remove('x');

События байндинга


При объявлении привязки генерируется два события: "bind" и "bind:KEY", где KEY — ключ связанного свойства.


this.on('bind:x', () => {
    alert('x is bound');
});

this.on('bind', evt => {
    alert(`${evt.key} is bound`);
});

this.bindNode('x', '.my-node');

Это событие может быть полезно, например, тогда, когда байндинги контролирует другой класс, и вам нужно запустить свой код после какой-нибудь привязки (например, привязки песочницы).


События добавления и удаления событий


image

Когда добавляется событие, генерируются события "addevent" и "addevent:NAME", когда удаляются — "removeevent" и "removeevent:NAME", где NAME — имя события.


this.on('addevent', handler);
this.on('addevent:someevent', handler);
this.on('removeevent', handler);
this.on('removeevent:someevent', handler);

Одним из способов применения можно назвать использование событий фреймворка в связке с движком событий сторонней библиотеки. Скажем, вы хотите разместить все обработчики для класса в одном единственном вызове on, сделав код читабельнее и комапактнее. С помощью "addevent" вы перехватываете все последующие инициализации событий, а в обработчике проверяете имя события на соответствие каким-нибудь условиям и инициализируете событие, используя API сторонней библиотеки. В примере ниже код из проекта, который юзает Fabric.js. Обработчик "addevent" проверяет имя события на наличие префикса "fabric:" и, если проверка пройдена, добавляет холсту соответствующий обработчик с помощью Fabric API.


this.canvas = new fabric.Canvas(node);
this.on({
    'addevent': evt => {
        const { name, callback } = evt;
        const prefix = 'fabric:';
        if(name.indexOf(prefix) == 0) {
            const fabricEventName = name.slice(prefix.length);
            // add an event to the canvas
            this.canvas.on(fabricEventName, callback);
        }
    },
    'fabric:after:render': evt => {
        this.data = this.canvas.toObject();
    },
    'fabric:object:selected': evt => { /* ... */ }
});

Делегированные события


Теперь приступим к самому интересному: к делегированием событий. Синтаксис делегированных событий таков: PATH@EVENT_NAME, где PATH — это путь (свойства разделены точкой) к объекту, на который навешивается событие EVENT_NAME. Давайте разберемся на примерах.


Пример 1


Вы хотите навешать обработчик события в свойстве "a", которое является объектом.


this.on('a@someevent', handler);

Обработчик будет вызван тогда, когда в "a" произошло событие "someevent".


this.a.trigger('someevent'); // если a - экземпляр Matreshka
Matreshka.trigger(this.a, 'someevent'); // если a - обычный объект или экземпляр Matreshka

При этом, обработчик можно объявить и до того, как свойство "a" объявлено. Если свойство "a" перезаписать другим объектом, внутренний механизм Matreshka.js отловит это изменение, удалит обработчик у предыдущего значения свойства и навешает новому значению.


this.a = new Matreshka();
this.a.trigger('someevent');
//или
this.a = {};
Matreshka.trigger(this.a, 'someevent');

Обработчик handler снова будет вызван.


Пример 2


А что если наш объект — коллекция, унаследованная от Matreshka.Array или Matreshka.Object (Matreshka.Object — это коллекция, типа ключ-значение)? Мы заранее не знаем, в каком элементе коллекции произойдет событие (в первом или десятом). Поэтому, вместо имени свойства, для этих классов, можно использовать звездочку "*", говорящую о том, что обработчик события должен вызываться тогда, когда событие вызвано на одном из входящих в коллекцию элементов.


this.on('*@someevent', handler);

Если входящий элемент — экземпляр Matreshka:


this.push(new Matreshka());
this[0].trigger('someevent');

Или, в случае, если входящий элемент либо обычный объект либо экземпляр Matreshka:


this.push({});
Matreshka.trigger(this[0], 'someevent');

Пример 3


Идем глубже. Скажем, у нас есть свойство "a", которое содержит объект со свойством "b", в котором должно произойти событие "someevent". В этом случае, свойства разделаются точкой:


this.on('a.b@someevent', handler);
this.a.b.trigger('someevent');
//или
Matreshka.trigger(this.a.b, 'someevent');

Пример 4


У нас есть свойство "a", которое является коллекцией. Мы хотим отловить событие "someevent", которое должно возникнуть у какого-нибудь элемента входящего в эту коллекцию. Совмещаем примеры (2) и (3).


this.on('a.*@someevent', handler);
this.a[0].trigger('someevent');
//или
Matreshka.trigger(this.a[0], 'someevent');

Пример 5


У нас есть коллекция объектов, содержащих свойство "a", являющееся объектом. Мы хотим навешать обработчик, на все объекты, содержащиеся под ключем "a" у каждого элемента коллекции:


this.on('*.a@someevent', handler);
this[0].a.trigger('someevent');
//или
Matreshka.trigger(this[0].a, 'someevent');

Пример 6


image

У нас есть коллекция, элементы которой содержат свойство "a", являющееся коллекцией. В свою очередь, последняя включает в себя элементы, содержащие свойство "b", являющееся объектом. Мы хотим отловить "someevent" у всех объектов "b":


this.on('*.a.*.b@someevent', handler);
this[0].a[0].b.trigger('someevent');
//или
Matreshka.trigger(this[0].a[0].b, 'someevent');

Пример 7. Различные комбинации


Кроме произвольных событий, можно использовать и встроенные в Matreshka.js. Вместо "someevent" можно воспользоваться событием "change:KEY", описанное выше или "modify", которое позволяет слушать любые изменения в Matreshka.Object и Matreshka.Array.


// в объекте "a" есть объект "b", в котором мы слушаем изменения свойства "c".
this.on('a.b@change:c', handler);

// объект "a" - коллекция коллекций
// мы хотим отловить изменения (добавление/удаление/пересортировку элементов) последних.
this.on('a.*@modify', handler); 

Напоминаю, что делегированные события навешиваются динамически. При объявлении обработчика, любая ветвь пути может отсутствовать. Если что-то в дереве объектов переопределено, связь со старым значением разрывается и создается связь с новым значением:


this.on('a.b.c.d@someevent', handler);
this.a.b = {c: {d: {}}};
Matreshka.trigger(this.a.b.c.d, 'someevent');

DOM события


Как известно, Matreshka.js позволяет связать DOM элемент на странице с каким-нибудь свойством экземпляра Matreshka или обычного объекта, реализуя одно или двух-стороннее связывание:


this.bindNode('x', '.my-node');
//или
Matreshka.bindNode(object, 'x', '.my-node');

Подробнее о методе bindNode.


До или после объявления привязки можно создать обработчик, слушающий DOM события привязанного элемента. Синтаксис таков: DOM_EVENT::KEY, где DOM_EVENT — DOM или jQuery событие (если jQuery используется), а KEY — ключ привязанного свойства. DOM_EVENT и KEY разделены двойным двоеточием.


this.on('click::x', evt => {
  evt.preventDefault();
});

В объект оригинального DOM события находится под ключём domEvent объекта события, переданного в обработчик. Кроме этого, в объекте доступно несколько свойств и методов, для того чтобы не обращаться каждый раз к domEvent: preventDefault, stopPropagation, which, target и несколько других свойств.


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


document.querySelector('.my-node').addEventListener('click', evt => {
    evt.preventDefault();
});

Делегированные DOM события


Объявление событий из примера выше требует объявления привязки. Вы должны совершить два шага: вызвать метод bindNode и, собственно, объявить событие. Это не всегда удобно, так как часто бывают случаи, когда DOM узел нигде не используется, кроме одного-единственного DOM события. Для такого случая предусмотрен еще один вариант синтаксиса DOM событий, выглядящий, как DOM_EVENT::KEY(SELECTOR). KEY, в данном случае — некий ключ, связанный с неким DOM элементом. а SELECTOR — это селектор DOM элемента, который входит в элемент, связанный с KEY.


<div class="my-node">
    <span class="my-inner-node"></span>
</div>

this.bindNode('x', '.my-node');
this.on('click::x(.my-inner-node)', handler);

Делегированные DOM события внутри песочницы


Если нам нужно создать обработчик для некоегого элемента, входящего в песочницу, используется немного упрощенный синтаксис DOM_EVENT::(SELECTOR).


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


this.bindNode('sandbox', '.my-node');
this.on('click::(.my-inner-node)', handler);

Этот код делает совершенно то же самое:


this.on('click::sandbox(.my-inner-node)', handler);

События класса Matreshka.Object


Напомню, Matreshka.Object — это класс, отвечающий за данные, типа ключ-значение. Подробнее об этом классе прочтите в документации.


При каждом измении свойств, отвечающих за данные, генерируется событие "set".


this.on('set', handler);

При каждом удалении свойств, отвечающих за данные, генерируется событие "remove".


this.on('remove', handler);

При каждом измении или удалении свойств, отвечающих за данные, генерируется событие "modify".


this.on('modify', handler);

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


События класса Matreshka.Array


С массивом всё намного интереснее. Matreshka.Array включает массу полезных событий, дающих возможность узнать что произошло в коллекции: вставка элемента, удаление элемента, пересортировка.


Напомню, Matreshka.Array — это класс, отвечающий за реализацию коллекций во фреймворке. Класс полностью повторяет методы встроенного Array.prototype, а программисту не нужно думать о том, какой метод вызвать, чтоб что-то добавить или удалить. Что нужно знать о событиях Matreshka.Array:


  • При вызове методов, позаимствованных у Array.prototype вызывается соответствующее событие ("push", "splice", "pop"...)
  • При вставке элементов в массив генерируются события "add" и "addone". Используя первое, в свойство "added" попадает массив из вставленных элементов. Используя второе в свойство "addedItem" попадает вставленный элемент, а событие генерируется столько раз, сколько элементов добавленно.
  • При удалении элементов используется та же логика: "remove" генерируется, передавая в свойство "removed" объекта события массив удаленных элемеентов, а "removeone" генерируется на каждом удаленном элементе, передавая в свойство "removedItem" удаленный элемент.
  • При любых модификациях коллекции генерируется событие "modify". Т. е. отлавливать события "remove" и "add" по отдельности не обязательно.

Несколько примеров из документации:


this.on('add', function(evt) {
    console.log(evt.added); // [1,2,3]
});
// обработчик запустится трижды,
// так как в массив добавили три новых элемента
this.on('addone', function(evt) {
  console.log(evt.addedItem); // 1 … 2 … 3
});
this.push(1, 2, 3);

Чтоб не копировать содержимое документации полностью, предлагаю ознакомиться с документацией к Matreshka.Array самостоятельно.


Спасибо всем, кто остаётся с проектом. Всем добра.

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

Круто?

Matreshka.js

24,68

JavaScript фреймворк для новичков

Поделиться публикацией

Похожие публикации

Комментарии 0

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

Самое читаемое