Документация на русском
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');Это событие может быть полезно, например, тогда, когда байндинги контролирует другой класс, и вам нужно запустить свой код после какой-нибудь привязки (например, привязки песочницы).
События добавления и удаления событий

Когда добавляется событие, генерируются события "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

У нас есть коллекция, элементы которой содержат свойство "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');До или после объявления привязки можно создать обработчик, слушающий 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 самостоятельно.
Спасибо всем, кто остаётся с проектом. Всем добра.
