Для страниц, у которых HTML уже готов: серверный рендеринг, виджеты, старые проекты, страница из одного HTML-файла. Подключите один тег <script> и привяжите свойства объекта к элементам прямо в JavaScript — разметку менять не нужно. Проект развивается с 2013 года; версия 3.0 — это один пакет с ES-модулями и типами TypeScript, без зависимостей.
Размер:matreshka.min.js весит 64,6 кБ в минифицированном виде, 20,9 кБ со сжатием gzip и 18,7 кБ с brotli — так CDN отдает его современным браузерам.
Matreshka.js связывает JavaScript-объекты с HTML, который уже есть на странице. Вы привязываете свойство к DOM-узлу, и с этого момента они синхронизированы. Меняете свойство — обновляется узел; вводите текст в узел — меняется свойство. Без шаблонов, без виртуального DOM и без сборки.
Привязка добавляет свойству геттер и сеттер. Присваивание app.x = 'hi' вызывает сеттер: он обновляет все узлы, привязанные к x, и генерирует событие change:x. Зависимые свойства, преобразование значений и коллекции построены на этом же механизме.
Страницы, HTML которых приходит с сервера: сайты с серверным рендерингом, темы CMS, старые приложения. Matreshka добавляет поведение к этой разметке, не забирая её под свой контроль.
Виджеты и отдельные HTML-файлы: один тег <script> с CDN, без npm и без сборщика.
Существующий код: привязка работает с любым обычным объектом, поэтому её можно добавлять по частям.
Компонентным фреймворком. В ней нет JSX, однофайловых компонентов и серверного рендеринга. Если вы пишете весь интерфейс на JavaScript с нуля, лучше подойдут React или Vue.
import Matreshka from 'matreshka';// или через CommonJS (Node.js 22.12+)const { Matreshka } = require('matreshka');
Роутер и парсер форм — в том же пакете: matreshka/router связывает свойства с частями URL, matreshka/parse-form связывает именованные поля формы со свойствами.
initRouter(object, '/a/b/') синхронизирует object.a и object.b с частями location.hash после #! (#!/foo/bar/); в URL значения закодированы, в свойствах — декодированы. Чтобы вместо этого использовать путь через History API, передайте 'history' третьим аргументом. parseForm(object, form) возвращает форму или null, если форма не найдена. Подробнее: Matreshka.initRouter, Matreshka.parseForm.
import { initRouter } from 'matreshka/router';import { parseForm } from 'matreshka/parse-form';
Без сборщика подключите браузерный бандл. Он определяет глобальную переменную Matreshka с роутером и парсером форм: Matreshka.initRouter, Matreshka.Router и Matreshka.parseForm. Бандл рассчитан на современные браузеры.
Без сборщика можно импортировать matreshka.min.mjs — тот же код одним ES-модулем: все именованные экспорты matreshka, а также initRouter, Router и parseForm. Это один запрос, и весит он столько же, сколько файл для тега script.
После того, как вы освоили необходимые для быстрого старта возможности, можете ознакомиться с другими, менее важными (но всё еще важными) свойствами и методами Matreshka.js.
Для того, чтоб не пугать новичков вещами, которые используются относительно редко, все неважные методы и свойства по умолчанию скрыты. Если вы хотите знать о фреймворке всё, без исключения, выберите на переключателе слева, в меню.
Внимание, если вы перейдете по ссылке к методу или свойству третьего уровня важности, то третий уровень включится автоматически.
Изучив самые важные свойства и методы, вы можете смело приступать к работе. С классами, методами и свойствами, помеченными значком необходимо разобраться в первую очередь.
После того, как вы освоили необходимые для быстрого старта возможности, можете ознакомиться с другими, менее важными (но всё еще важными) свойствами и методами Matreshka.js.
Для того, чтоб не пугать новичков вещами, которые используются относительно редко, все неважные методы и свойства по умолчанию скрыты. Если вы хотите знать о фреймворке всё, без исключения, выберите на переключателе слева, в меню.
Внимание, если вы перейдете по ссылке к методу или свойству третьего уровня важности, то третий уровень включится автоматически.
Написать первое приложение с помощью фреймворка Matreshka.js очень просто.
1. Создайте HTML файл со следующим содержимым
<!DOCTYPE html><html> <head> <title>Моё первое приложение на базе Matreshka.js</title> </head> <body> <input type="text" class="my-input"> <div class="my-output"></div> <script src="https://cdn.jsdelivr.net/npm/matreshka@3/dist/matreshka.min.js"></script> <script src="js/app.js"></script> </body></html>
2. Создайте класс в файле js/app.js
// сохраняем html байндер в переменную с коротким именемconst htmlBinder = Matreshka.binders.html;// создаём класс, который наследуется от Matreshkaclass Application extends Matreshka { constructor() { super(); // связываем свойство x и текстовое поле this.bindNode('x', '.my-input'); // связываем свойство x и блок с классом my-output this.bindNode('x', '.my-output', htmlBinder()); // слушаем изменения свойства x this.on('change:x', () => console.log(`x изменен на "${this.x}"`)); }}const app = new Application();
3. Это всё!
Теперь можете открыть консоль разработчика (клавиша F12) и написать:
Как правило, этот класс, (как и Matreshka.Array и Matreshka.Object), не используются напрямую. Вместо этого, от него наследуются классы, создаваемые разработчиком.
bindNode – Связывает свойство объекта с HTML элементом
bindOptionalNode – Работает в точности так же, как и Matreshka#bindNode но не бросает исключение, если аргумент node - пустой массив, undefined или не существует
Matreshka#bindNode - это единственный метод класса Matreshka, отвечающий за изменения DOM. Он создаёт мост между значением свойства и состоянием HTML элемента на странице: от простого инпута до сложного виджета (сложность элементов не ограничена). После связывания свойства экземпляра и HTML элемента не нужно больше следить за синхронизацией данных и представления.
Обратите внимание, что у метода есть статичный аналог, который работает в точности так же, но принимает любой целевой объект в качестве первого аргумента, сдвигая остальные аргументы вправо.
import { bindNode } from 'matreshka';const object = {};bindNode(object, key, node, binder, eventOptions);// вместо this.bindNode(key, node, binder, eventOptions);
Для двустороннего связывания элемента и значения свойства, в метод передаются три аргумента: имя свойства, HTML элемент и правило привязки (байндер, биндер, binder, привязчик). Байндер, в свою очередь, является обычным объектом и может иметь следующие свойства: on, getValue, setValue, initialize, destroy (подробнее см. binder). Все пять свойств опциональны. Это позволяет также объявлять и односторонние привязки.
Метод bindNode поддерживает привязки "многие ко многим". С одним свойством можно связать несколько элементов, а с одним элементом можно связать несколько свойств, в том числе и от разных экземпляров разных классов.
Например, вы хотите связать свойство объекта с элементом input[type="checkbox"]:
this.bindNode('myKey', '.my-checkbox', { // когда менятся состояние элемента? // - по событию 'click' on: 'click', // как извлечь состояние элемента? // - вернуть значение 'checked' getValue() { return this.checked; }, // как установить состояние элемента? // - установить значение 'checked' setValue(v) { this.checked = !!v; }});
После объявления привязки можно устанавливать значение свойства объекта самым привычным способом, а элемент изменит своё состояние автоматически. При клике на чекбокс, значение свойства тоже изменится на соответствующее.
// устанавливает checked = truethis.myKey = true;
Более сложный пример: связывание свойства объекта с виджетом jQuery UI
<div class="my-slider"></div>
this.bindNode('myKey', '.my-slider', { // когда менятся состояние элемента? // - по событию 'slide' on: 'slide', // как извлечь состояние элемента? // - вернуть значение виджета 'value' getValue() { return $(this).slider('option', 'value'); }, // как установить состояние элемента? // - установить значение 'value' setValue(v) { $(this).slider('option', 'value', v); }, // как инициализировать виджет? // инициализировать слайдер можно любым способом, // но initialize предоставляет немного синтаксического сахара initialize() { $(this).slider({ min: 0, max: 100 }); }});
// установит значение слайдера 42this.myKey = 42;
Выглядит просто, но вы, скорее всего, задаётесь вопросом: "Как сделать так, чтоб мне не пришлось каждый раз прописывать эти правила?". Действительно, на странице может быть очень много однотипных элементов: текстовых полей, выпадающих меню, новых полей из спецификации HTML5, могут быть и сторонние виджеты (о чем говорит пример выше).
Как видно из документации к аргументам метода bindNode, третий аргумент не обязателен. Этот вопрос решает массив Matreshka.defaultBinders, который содержит функции, проверяющие HTML элемент на соответствие заданным правилам и возвращающие соответствующий байндер или undefined. Появляется возможность многократно сократить код, вынося правила привязки в отдельную часть вашего кода, а для привязки использовать синтаксис без третьего аргумента:
this.bindNode('myKey', '.my-element');
Как это сделать? Нужно добавить функцию, проверяющую ваш элемент на соответствие некоторым правилам в начало массива Matreshka.defaultBinders.
const checkboxBinder = () => { return { on: 'click', getValue() { return this.checked; }, setValue(v) { this.checked = !!v; } }};// метод unshift добавляет функцию// в начало массива Matreshka.defaultBindersMatreshka.defaultBinders.unshift(node => { // проверяем, является ли элемент чекбоксом if(node.tagName === 'INPUT' && node.type === 'checkbox') { // если проверка пройдена, возвращаем новый байндер return checkboxBinder(); }});
Что делать, если вам нужно передать аргументы для инициализации какого-нибудь плагина или виджета? Можно вручную вызывать функцию, возвращающую байндер.
Matreshka.defaultBinders из коробки содержит поддержку всех без исключения HTML элементов форм: select (включая multiple), textarea, output, input (в том числе и все типы из спецификации HTML5: text, checkbox, radio, range, number, date, search, time, datetime, datetime-local, color и остальных). Это значит, что для стандартных элементов указывать байндер не обязательно.
После привязки, вам доступен новый нестандартный CSS селектор :bound(KEY).
this.bindNode('myKey', '.my-element');// найдет элемент '.my-inner-element' внутри '.my-element'this.bindNode('myAnotherKey', ':bound(myKey) .my-inner-element');
Если элемент не найден, бросается исключение Binding error: node is missing for key "KEY". Для того, чтоб избежать ошибки используйте метод Matreshka#bindOptionalNode
Matreshka#bindNode умеет ассоциировать экземпляр класса с "главным" HTML элементом, создавая так называемую песочницу. Это нужно для того, чтоб ограничить влияние объекта одним HTML элементом. Для привязки песочницы используется специальное свойство sandbox.
<div class="my-sandbox"> <!-- your HTML code --></div>
this.bindNode('sandbox', '.my-sandbox');
Определение песочницы добавляет множество удобств программисту. Например:
Добавляет синтаксический сахар для делегированных DOM событий в методе Matreshka#on
Следует иметь в виду, что только один HTML элемент может быть связан со свойством sandbox, иначе бросается ошибка. Для объявления песочницы можно воспользоваться методом Matreshka#bindSandbox. Перед тем, как объявить байндинг, метод отвязывает предыдущую песочницу.
// объявляем песочницуthis.bindNode('sandbox', '.my-sandbox');// .my-element ищется в песочницеthis.bindNode('myKey', ':sandbox .my-element');// для делегированных событий внутри песочницы не требуется указывать ключthis.on('click::(.my-button)', () => { ... });// выведет в консоль элемент .inner-node,// который находится внутри песочницыconsole.log(this.$('.inner-node'));
Четвертым аргументом eventOptions в метод bindNode можно передать объект, состоящий из флагов, описанных ниже, глобальных флагов (например, silent), или кастомных данных, которые попадут в обработчики событий bind и bind:KEY.
Если в качестве key передать строку, содержащую точку, то такая строка будет интерпритирована как путь к свойству во вложенном объекте. фреймворк будет слушать изменения во всём дереве, разрывая связь для старых ветвей, и создавая её для новых.
this.a = { b: { c: 'foo' } };this.bindNode('a.b.c', node);this.a.b.c = 'bar'; // обновит элемент значением barconst oldB = this.a.b;this.a.b = { c: 'baz' }; // обновит элемент значением baz // элемент не обновится, так как связь с этой ветвью разорванаoldB.c = 'fuu';
В случае, если имя свойства должно быть использовано как есть, воспользуйтесь флагом exactKey со значением true.
При наличии у байндера getValue, состояние элемента будет извлечено и присвоено свойству сразу после вызова bindNode при условии, если привязываемое свойство имеет значение undefined. Для того, чтоб форсировать это поведение даже если свойство - не undefined используйте флаг getValueOnBind со значением true. Для отмены этого поведения, используйте тот же флаг со значением false.
При наличии у байндера setValue, значение свойства будет установлено в качестве состояния элемента сразу после вызова bindNode при условии, если привязываемое свойство имеет значение отличное от undefined. Для того, чтоб форсировать это поведение даже если свойство - undefined используйте флаг setValueOnBind со значением true. Для отмены этого поведения, используйте тот же флаг со значением false.
Важной особенностью метода bindNode является то, что к изменению свойств и изменению состояния элемента применяется микропаттерн debounce. Это значит, что если привязанное свойство будет изменено многократно за короткий промежуток времени (например, в цикле), состояние элемента будет обновлено лишь один раз после задержки в несколько миллисекунд (благодаря debounceSetValue=true). И наоборот: если состояние элемента меняется многократно за короткий промежуток времени (т. е. вызывается соответствующее DOM событие), свойство получит новое значение только один раз после короткой зарержки (благодаря debounceGetValue=true).
Как говорилось выше, к изменению свойств и изменению состояния элемента применяется микропаттерн debounce. Это не касается самого момента связывания. При вызове bindNode установка состояния элемента или его извлечение с изменением свойства происходит синхронно. debounceSetValueOnBind и debounceGetValueOnBind установленные как true включают debounce и для этих процессов.
Эти флаги позволяют указать задержку debounce. debounceSetValueDelay задаётся при использовании debounceSetValue и debounceSetValueOnBind, debounceGetValueDelay при использовании debounceGetValue и debounceGetValueOnBind.
Даже если в метод bindNode передать конкретный байндер, фреймворк попытается отыскать байндер из Matreshka.defaultBinder и расширить его свойствами переданного объекта. Такая возможность позволяет использовать дефолтный байндер, который частично переопределен.
Например, мы хотим связать input[type="text"] со свойством. По умолчанию, стандартный байндер для этого элемента содержит свойство "on" со значением "input". Это значит, что значение свойства экземпляра и состояние элемента будут синхронизированы сразу после ввода или удаления символа пользователем. В случае, если вы хотите, чтоб синхронизация происходила по DOM событию "blur", вам потребуется передать третьим аргументом объект, содержащий единственное свойство "on". Этот объект объединится со стандартным байндером, сохранив при этом значения getValue и setValue.
this.bindNode('x', '.my-input', { on: "blur" });
Для отмены этого поведения и использования байндера как он есть, в объект события можно передать флаг useExactBinder со значением true.
В метод Matreshka#bindNode можно передать объект чтобы избежать многократного вызова метода и сократить код. Ключи объекта - это имена привязываемых свойств, а значения могут быть следующими:
HTML элемент
Объект со свойствами node (HTML элемент) и binder
Массив объектов со свойствами node (HTML элемент) и binder
Если binder передан вторым аргументом, то он служит байндером для тех элементов, для которых байднер не указан явно.
this.bindNode({ foo: { node: ':sandbox .aaa', binder: Matreshka.binders.html() }, bar: '.bbb', baz: [{ node: '.ccc' }, { node: document.querySelector('.ddd'), binder: Matreshka.binders.prop('baz') }]}, { // will be used as a binder for .bbb and .ccc setValue(value) { foo(value); }});
Вариация метода позволяет передать массив объектов, содержащих информацию об одном байндинге каждый. Элемент массива должен содержать следующие свойства:
key - имя свойства
node - элемент, для которого объявляем связывание с key
binder - байндер (не обязательно)
event - объект события (не обязательно)
Второй аргумент - общий объект события, расширяющий event для каждого байндинга (свойства из event приоритетнее).
Создает зависимость значения одного свойства от значений других
Метод calc создает зависимость значения свойства (аргумент target) от значений других свойств (аргумент source). При изменении source, target вычисляется автоматически.
Обратите внимание, что у метода есть статичный аналог, который работает в точности так же, но принимает любой объект в качестве первого аргумента, сдвигая остальные аргументы вправо.
import { calc } from 'matreshka';const object = {};calc(object, target, source, handler, eventOptions);// вместо this.calc(target, source, handler, eventOptions);
Из соображений чистоты кода, комбинировать строки и объекты в массиве source не рекомендуется. Вместо строк лучше передать объект, у которого свойство object равно целевому объекту. Пример ниже делает то же самое, что и предыдущий.
this.b = 1;this.c = 2;const someObject = { d: 3, e: 4};this.calc('a', [{ object: this, // целевой объект - это this key: ['b', 'c']}, { object: someObject, key: ['d', 'e']}], (b, c, d, e) => b + c + d + e);console.log(this.a); // 10
Метод защищен от цикличных зависимостей (например a зависит от b, b зависит от c, а c зависит от a) и при ошибке вычислений не блокирует страницу и не бросает исключение о переполнении стека.
Как вы могли заметить, аргументы функции handler всегда расположены в том же порядке, что и свойства из source.
В случае, если нужно изменить значение свойства-источника и сделать это так, чтоб целевое свойство не было вычислено заново, используйте метод Matreshka#set с флагом skipCalc равным true.
Четвертым аргументом в метод calc можно передать объект события eventOptions, содержащий специальные флаги, которые описаны ниже, флаги для метода set (например, silent), либо кастомные данные, которые передаются в обработчик события change:KEY.
Если источник меняется с флагом silent, цель тоже меняется «тихо». Кастомные данные изменения источника тоже попадают в обработчики цели. Флаги force, forceHTML, silentHTML и skipMediator изменения источника на цель не действуют.
При вызове метода calc целевое свойство вычисляется без задержек, но при изменении свойства-источника применяется паттерн debounce. Это значит, что целевое свойство изменится через несколько миллисекунд и только один раз, даже если свойства-источники изменились многократно за короткий промежуток времени.
this.b = 1;this.c = 2;this.d = 3;this.calc('a', ['b', 'c', 'd'], (b, c, d) => b + c + d);this.on('change:a', () => { // обработчик будет вызван один раз несмотря на то, // что свойства-источники изменились трижды console.log(`a is changed to ${this.a}`); // a is changed to 60});this.b = 10;this.c = 20;this.d = 30;console.log(this.a); // 6 вместо 60
Для отмены debounce при изменении свойств, т. е. для включения моментального обновления целевого свойства (как было в предыдущих версиях метода), четвертым аргументом метода calc можно передать флаг debounceCalc равный false.
this.b = 1;this.c = 2;this.d = 3;this.calc('a', ['b', 'c', 'd'], (b, c, d) => b + c + d, { debounceCalc: false});this.on('change:a', () => { // обработчик будет вызван трижды, // каждый раз когда свойства b, c или d меняются // a is changed to... 15, 33, 60 console.log(`a is changed to ${this.a}`);});this.b = 10;this.c = 20;this.d = 30;console.log(this.a); // 60
Как говорилось выше, при вызове метода calc целевое свойство вычисляется сразу, а при изменении свойств-источников - нет. Включить
debounce и при первом вычислении свойства можно передав в качестве свойства объекта события флаг debounceCalcOnInit=true.
this.on('change:a', () => { // обработчик будет вызван один раз через небольшой промежуток времени console.log(`a is changed to ${this.a}`); // a is changed to 6});this.b = 1;this.c = 2;this.d = 3;this.calc('a', ['b', 'c', 'd'], (b, c, d) => b + c + d, { debounceCalcOnInit: true});console.log(this.a); // undefined
На практике debounceCalcOnInit вряд ли пригодится. Нужно лишь помнить, что есть флаг включающий "полный debounce".
Известно, что при вызове метода calc свойство сразу получает новое значение. Для того, чтоб отменить это поведение и вычислить свойство target только тогда, когда свойство-источник впервые изменено, используйте флаг setOnInit равный false.
this.calc('a', 'b', b => b * 2, { setOnInit: false});console.log(this.a); // undefined// но если обновить this.b, для свойства a будет вычислено новое значениеthis.b = 1;
Как говорилось выше, в качестве имени свойства-источника можно передать строку, содержащую точки. Такое имя будет истолковано как путь к свойству во вложенном объекте. В случае, если имя должно быть использовано как есть, воспользуйтесь флагом exactKey со значением true.
Этот флаг позволяет использовать экземпляры Promise внутри функции, вычисляющей значение. Значением искомого свойства становится результат успешного выполнения промиса.
Внимание! Promise невозможно отменить. Используйте возможность promiseCalc аккуратно и не допускайте многократного вызова тяжелых функций.
Если источники меняются снова до того, как промис выполнится, цель устанавливает только последнее вычисление: результаты предыдущих отбрасываются.
this.calc('a', ['b', 'c'], (b, c) => { return new Promise(resolve => { setTimeout(() => { resolve(b + c) }, 1000); });}, { promiseCalc: true});this.b = 1;this.c = 2;// "a" изменится через секунду
Метод создаёт и фиксирует экземпляр класса в качестве значения заданного свойства. При попытке переопределить свойство, вместо самого переопределения, экземпляр обновляется. Таким образом достигается целостность приложения, сделанном на базе Matreshka.
Метод является надстройкой над Matreshka#mediate и переопределяет медиатор.
Экземпляр класса создается во время запуска метода instantiate. Первым аргументом конструктора класса становится текущее значение свойства. В конструкторе класса необходимо предусмотреть то, что в него попадет либо undefined (если свойство не содержало до этого никаких данных), либо объект, с которым нужно что-то сделать (например, расширить экземпляр класса свойствами объекта).
На деле это выглядит просто: вы создаете обычный класс, который почти всегда принимает какие-нибудь данные, которые нужно обработать (например, использовать их в методе Matreshka.Object#setData).
При попытке присвоить свойству другое значение, внутренний механизм метода instantiate, вместо присваивания, делает следующее:
Если указана функция updateCallback, метод запускает его с двумя аргументами: текущим значением свойства и данными, которые код пытается присвоить.
Если не указана функция updateCallback и если класс не унаследован от Matreshka.Object или Matreshka.Array, экземпляр расширяется свойствами объекта, который код пытается присвоить.
Особенностью метода является отсутствие ограничений на источник класса. В качестве класса может выступать любая функция-конструктор. которая инициализируется с помощью оператора new, а не только наследники Matreshka.
Функция, вызывающаяся при каждой попытке присвоить новые данные свойству, позволяющая кастомизировать логику обновления экземпляра класса новыми данными. Функция принимает два аргумента: текущее значение свойства (экземпляр класса) и данные, которые пытаются присвоить.
Получение родителя и имени свойства. Кроме данных (первый аргумент), в конструктор создаваемого класса, передается ссылка на объект, вызвавший instantiate и имя созданного свойства
class MyClass extends Matreshka { constructor(data, parent, key) { super(); // parent - это экземпляр MyParentClass, // который создал свойство // key - имя свойства ("х") }}class MyParentClass extends Matreshka { constructor() { super().instantiate('x', MyClass); }}
Нестандартный способ использования updateCallback для игнорирования любых изменений свойства.
this.instantiate('x', SubClass, () => {});
В случае, если ваш класс не подерживает использование оператора new, вместо instantiate воспользуйтесь методом Matreshka#mediate.
Трансформирует значение свойства при его изменении
Этот метод используется для преобразования значения свойства при его изменении. Например, вам нужно, чтоб значение свойства всегда было либо определенного типа, либо целым числом, либо быть не менее нуля и не более ста и т. д.
Обратите внимание, что у метода есть статичный аналог, который работает в точности так же, но принимает любой объект в качестве первого аргумента, сдвигая остальные аргументы вправо.
import { mediate } from 'matreshka';const object = {};mediate(object, key, mediator);// вместо this.mediate(key, mediator);
Функция-посредник (медиатор, mediator), возвращающая новое значение. В неё передаются следующие аргументы: новое значение, предыдущее значение, имя свойства, сам объект
Удаляет созданный ранее обработчик. Все три аргумента опциональны. Вы можете удалить как все события (не передавая ни одного аргумента), так и отдельные (передав только имя события, передав имя события и обработчик, передав и имя события, и обработчик, и контекст)
Без аргументов off удаляет все обработчики, добавленные через on, once и onDebounce, и генерирует removeevent для каждого из них. Привязки, зависимости calc и внутренние события Matreshka.Object и Matreshka.Array остаются.
Метод Matreshka#on добавляет обработчик события для экземпляра класса Matreshka. Полный список возможных событий с описанием см. здесь: eventNames.
Обратите внимание, что у метода есть статичный аналог, который работает в точности так же, но принимает любой объект в качестве первого аргумента, сдвигая остальные аргументы вправо.
import { on } from 'matreshka';const object = {};on(object, names, callback, triggerOnInit, context);// вместо this.on(names, callback, triggerOnInit, context);
Повторное добавление того же обработчика с тем же контекстом для того же события ничего не меняет. Для DOM событий (click::x) действует то же правило.
Добавляет обработчик события, вызываемый лишь однажды за определенный промежуток времени
Метод позволяет добавить обработчик события на экземпляр класса Matreshka, устраняя "дребезжание" обработчика. Функция может быть вызвана лишь один раз за определенный промежуток времени. В остальном, метод работает так же, как и Matreshka#on.
Парсит DOM дерево, объявляя привязки свойств, заключенных в двойные фигурные скобки.
Matreshka.js включает в себя простой DOM парсер, обрабатывающий синтаксические конструкции, заключенные в двойные фигурные скобки. Метод parseBindings первым аргументом принимает HTML строку, DOM узел или селектор, соответствующий DOM узлу.
Так как метод является DOM шаблонизатором (а не строковым HTML шаблонизатором), все дочерние DOM узлы переданного элемента остаются в своём прежнем состоянии (например, DOM события не затираются).
<!--Создаст текстовый узел на месте {{user.name}}и свяжет свойство name из объекта user с этим узломJS: this.user = {name: 'Joe'}--><span>Hello, {{user.name}}</span>
Привязка элементов форм.
<!--Свяжет свойство "x" экземпляра с текстовымполем (двусторонняя привязка)JS: this.x = 'some value';--><input type="text" value="{{x}}">
<!--Для привязки textarea и select нужно использовать атрибут value--><textarea value="{{x}}"></textarea><select value="{{x}}">...</select>
<!--Свяжет свойство "x" экземпляра с чекбоксом(двусторонняя привязка)JS: this.x = true;--><input type="checkbox" checked="{{x}}">
В случае, если вы разрабатываете большую форму со стандартными HTML5 полями, метод поможет сохранить время на объявление многочисленных привязок. Кроме этого, parseBindings полезен в случае создания очень простой коллекции, не требующей реализации сложной модели.
Возвращает:$nodes – Коллекция DOM узлов, переданная в функцию в качестве аргумента
this.bindNode('sandbox', '.app');this.select('.my-element');// то же самое, что иthis.nodes.sandbox.querySelector('.my-element');// и то же самое, что и$('.app').find('.my-element')[0];
Возвращает элементы из песочницы, соответствующие селектору
Синоним: Matreshka#$
После создания песочницы методом Matreshka#bindNode, можно получать и использовать элементы, находящиеся в ней. Кроме этого, метод поддерживает селектор :bound(KEY).
this.bindNode('sandbox', '.app');nodes = this.selectAll('.my-element');// то же самое, что иnodes = this.$('.my-element'); // $ - ссылка на метод selectAll// и то же самое, что иnodes = $('.app').find('.my-element');
Селектор :bound(KEY)
this.bindNode('myKey', '.my-element');nodes = this.selectAll(':bound(myKey) .my-another-element');// и то же самое, что иnodes = $('.my-element').find('.my-another-element');
Устанавливает значение свойства, позволяя передать объект события в качестве третьего аргумента
Список поддерживаемых флагов:
silent - не вызывать события change и change:KEY
silentHTML - не менять состояние привязанных элементов
force - вызвать события change и change:KEY даже если значение свойства не изменилось
forceHTML - изменить состояние привязанного элемента, даже если значение свойства не изменилось. Эта опция нужна, если привязанный элемент был отрисован после привязки (например, в select были добавлены теги option)
skipMediator - предотвращает трансформацию свойства медиатором (см. Matreshka#mediate)
skipCalc - предотвращает работу зависимостей, созданных с помощью Matreshka#calc
this.on('change:myKey', evt => { alert(evt.value);});// то же самое, что и this['myKey'] = 3// или this.myKey = 3// выводит на экран 3this.set('myKey', 3);
Используя этот метод, можно удалить недавно добавленную, но уже не нужную связь между свойством и элементом.
Ключ с точками — это путь к вложенному свойству, как и в Matreshka#bindNode; с опцией события exactKey: true он используется как есть. null вместо ключа удаляет также привязки вложенных свойств ("a.b").
Объект события, в который можно передать какие-нибудь данные для обработчика или ключ "silent", который отключает генерацию событий "unbind" и "unbind:KEY"
this.bindNode('myKey', '.my-element');// меняет значение свойства и состояние HTML элементаthis.myKey = true;this.unbindNode('myKey', '.my-element');// теперь меняется только значение свойстваthis.myKey = false;
Вариация метода позволяет передать масссив объектов, содержащих информацию об одном байндинге каждый. Элемент массива должен содержать следующие свойства:
key - имя свойства
node - элемент, для которого был объявле байндинг с key (не обязательно)
Эта вариация метода удобна тем, что её синтаксис совпадает с одной из вариаций метода Matreshka#bindNode, позволяя присвоить байндинги переменной для быстрого удаления.
Ключ sandbox зарезервирован для привязки: он не попадает в Object.keys и JSON.stringify, а чтение или присваивание this.sandbox бросает ошибку. Привязанный узел доступен как this.nodes.sandbox.
Объект содержит привязанные элементы для быстрого доступа, в виде отдельных DOM узлов.
Обратите внимание, каждое свойство объекта содержит первый узел из связанных с соотвутствующим свойством. Для получения всех узлов, связанных с определенным свойством, используйте Matreshka#$nodes.
Функция принимает любой объект и возвращает экземпляр недоступного извне класса, который перенимает универсальные методы, позволяя их цепной вызов для работы с переданным объектом.
const object = {};Matreshka.chain(object) .calc('a', 'b', b => b * 2) .set('b', 3) .bindNode('c', '.node');// то же самое, что и// Matreshka.calc(object, 'a', 'b', b => b * 2)// Matreshka.set(object, 'b', 3)// Matreshka.bindNode(object, 'c', '.node');
Реализация классов, основанная на прототипном наследовании
Функция Class позволяет использовать классическое ООП в тех случаях, когда нельзя воспользоваться синтаксисом ECMAScript 2015 classes.
Если у prototype нет собственного свойства constructor, класс вызывает родительский конструктор с теми же аргументами, как класс ECMAScript 2015 без конструктора. В качестве constructor можно передать класс ECMAScript 2015: он вызывается при создании экземпляра, но методы, объявленные в его теле, не наследуются; добавьте их в prototype.
Возвращает:class – class (точнее, конструктор класса)
const A = Matreshka.Class({ method1() { ... }});const B = Matreshka.Class({ // B наследуется от A extends: A, method2() { ... }});const C = Matreshka.Class({ // С наследуется от B extends: B, method2() { // вызов родительского метода B.prototype.method2.apply(this, arguments); }, method3(a, b) { ... }});const D = Matreshka.Class({ // D наследуется от C extends: C, method3(a, b) { // вызов родительского метода C.prototype.method3.apply(this, arguments); }});
Передача объекта со статичными методами и свойствами
Связывает элемент со свойством, но не бросает исключение, если аргумент node - пустой массив, undefined или не существует
Этот статичный метод работает так же, как и Matreshka#bindOptionalNode и все его вариации, но принимает в качестве первого аргумента любой JavaScript объект.
Связывает свойство sandbox с элементом и отвязывает предыдущий элемент, если таковой существует
Этот статичный метод работает так же, как и Matreshka#bindSandbox и все его вариации, но принимает в качестве первого аргумента любой JavaScript объект.
Этот статичный метод работает так же, как и Matreshka#instantiate и все его вариации, но принимает в качестве первого аргумента любой JavaScript объект.
Включает двустороннее связывание данных между свойствами и частями URL
По умолчанию роутер работает с хешем: он использует location.hash с hashbang (#!/a/b/). Чтобы вместо этого использовать путь (/a/b/) через History API, передайте "history" третьим аргументом.
// используется location.hashinitRouter(object, '/a/b/c/');object.a = 'foo';object.b = 'bar';object.c = 'baz';// location.hash становится #!/foo/bar/baz/
Если нужно использовать History API вместо хеша, передайте "history" третьим аргументом.
initRouter(object, '/a/b/c/', 'history');
Как работает «традиционный» роутинг? Разработчик задает правило (маршрут) и функцию, которая вызывается, когда текущий путь подходит под правило.
route("books/:id", id => { // что-то делаем});
Принцип initRouter другой. Вы указываете, какая часть URL (поддерживаются и хеш, и HTML5 History) синхронизируется с каким свойством.
Скажем, нужно синхронизировать "x" с первой частью location.hash, а "y" со второй.
initRouter(object, '/x/y/');
Теперь, когда вы пишете...
object.x = 'foo';object.y = 'bar';
...location.hash автоматически меняется на #!/foo/bar/
И наоборот. Когда URL меняется вручную или кнопками «назад» и «вперед», свойства меняются автоматически.
location.hash = '#!/baz/qux/';// ... через мгновениеconsole.log(object.x, object.y); // 'baz', 'qux'
Как обычно, изменения свойств можно слушать методом Matreshka#on.
Matreshka.on(object, 'change:x', handler);// для экземпляров Matreshka: this.on('change:x', handler);
Значения кодируются в URL и декодируются обратно: object.x = 'a b/c' дает #!/a%20b%2Fc/, а object.x остается 'a b/c'. Хеш, который не начинается с #! (якорь вроде #top), не является маршрутом.
1. Если у свойства есть значение (не undefined, null и не ''), URL меняется сразу после вызова initRouter. Свойство без значения получает значение из URL.
object.x = 'foo';initRouter(object, '/x/y/');
2. Если свойство получает null, undefined или '', все следующие за ним свойства получают null.
initRouter(object, '/x/y/z/u/');object.y = null; // object.z и object.u тоже становятся null
Идея в том, чтобы свойства отражали актуальное состояние URL. Было бы странно получить "z" со значением "foo", когда соответствующей части URL нет.
Роутер поддерживает и HTML5 History. Для этого передайте функции initRouter необязательный аргумент type со значением "history" (по умолчанию type равен "hash").
initRouter(object, '/x/y/z/', 'history');
Роутер читает и пишет location.pathname; строка запроса и хеш остаются как есть.
На странице с бандлом, подключенным тегом script, функция доступна как Matreshka.initRouter.
Возвращает байндер, соответствующий элементу. Если таковой не найден, возвращает undefined. Функция перебирает Matreshka.defaultBinders для поиска байндера.
Добавляет обработчик события, вызываемый лишь однажды за определенный промежуток времени
Этот статичный метод работает так же, как и Matreshka#onDebounce и все его вариации, но принимает в качестве первого аргумента любой JavaScript объект.
Парсит DOM дерево, объявляя привязки свойств, заключенных в двойные фигурные скобки.
Этот статичный метод работает так же, как и Matreshka#parseBindings и все его вариации, но принимает в качестве первого аргумента любой JavaScript объект.
Возвращает:$nodes – Коллекция обработанных DOM узлов, переданная в функцию в качестве аргумента
Только массивы и экземпляры Matreshka.Array становятся Matreshka.Array; любой другой объект, даже со свойством length, становится Matreshka.Object. Из переданного Matreshka.Object берутся только ключи, отвечающие за данные.
Этот статичный метод работает так же, как и Matreshka#unbindNode и все его вариации, но принимает в качестве первого аргумента любой JavaScript объект.
Массив функций, возвращающих соответствующий байндер или undefined
defaultBinders - массив функций, которые по очереди проверяют элемент на соответствие заданным в этих функциях правилам и возвращающих байндер (см. binder). Этот массив используется тогда, когда в метод Matreshka#bindNode не был передан третий аргумент. Подробную информацию о привязках смотрите в документации к Matreshka#bindNode.
Matreshka.defaultBinders.unshift(element => { // проверяем, есть ли у элемента класс "foo" if(element.classList.contains('foo')) { // если проверка пройдена, возвращаем новый байндер return { on: ..., getValue: ..., setValue: ... }; }});// ...this.bindNode('myKey', '.foo.bar');
Объект parserBrackets позволяет изменить стандартный синтаксис парсера привязок. Он содержит два свойства: left (левая скобка, по умолчанию "{{") и right (правая скобка, по умолчанию "}}")
Matreshka.Object - класс, который отвечает за данные вида ключ-значение. Его задачей является отделение служебных свойств от данных, которые можно передать на сервер или сохранить в локальном хранилище. Класс наследуется от класса Matreshka и включает все его свойства и методы.
Представьте себе, что вы создаёте класс, включающий свойства "a", "b" и "c". Допустим "a" и "b" - свойства которые должны быть отправлены на сервер, а свойство "c" лишь отвечает за некоторое состояние приложения (например, содержит сумму "a" и "b"). Свойство "c" не нужно серверу. Поэтому нам нужно отделить свойства отвечающие за данные от свойств, которые таковыми не являются.
Для того, чтоб отделить такие свойства от остальных, можно воспользоваться методом Matreshka.Object#addDataKeys.
Если вы заранее не знаете, какие свойства являются данными, можно всегда использовать метод Matreshka.Object#setData, который не только объявляет свойства, отвечающие за данные, но и сразу устанавливает значения.
this.setData({ a: 1, b: 2});this.c = 3;
После того, как приложение получило информацию о том, что является данными, экземпляр Matreshka.Object можно сконвертировать в обычный объект методом Matreshka.Object#toJSON и передать на сервер или сохранить в локальной БД (например, в localStorage).
При добавлении и изменении свойств, отвечающих за данные генерируется события set и modify, при удалении - remove и modify. Т. е. любые изменения данных можно слушать с помощью modify.
this.on('modify', () => { alert('Object is modified');});
Если передать флаг replaceData, установленный как true в объект события, то свойства, которые не входят в переданный объект, будут удалены из списка свойств, отвечающих за данные
Метод работает рекурсивно, вызывая toJSON для всех свойств, у которых есть метод с таким именем. Для отмены рекурсии передайте false первым аргументом.
Класс Matreshka.Array служит коллекцией во фреймворке Matreshka.js. Он наследуется от класса Matreshka и включает все его свойства и методы. Кроме этого, Matreshka.Array имеет все методы, которые есть у обычного массива, тем самым упрощая изучение его возможностей.
Программист, знакомый с методами нативного Array сразу может понять, каким методом можно добавить элемент (push, unshift, splice), каким удалить (pop, shift, splice), каким отсортировать (sort, reverse) и т. д.
По причине того, что методы работают так же, как и оригинальные (с небольшими исключениями), они не приведены в этой документации по отдельности, а выведены в раздел Matreshka.Array#METHOD.
Для этого используется синтаксис метод_, где нижнее подчеркивание в конце имени метода означает, что последним аргументом является объект события. Такие методы не приведены в этой документации, так как требуется запомнить только их синтаксис. См. Matreshka.Array#METHOD_.
При использовании методов, позаимствованных у встроенного Array генерируются события с соответствующим именем. Вызывая метод push, генерируется событие push, вызывая метод shift генерируется событие shift, вызывая метод sort, генерируется событие sort и так далее.
this.on('push', evt => { console.log('push is called');});this.push(1, 2, 3);
При добавлении элементов генерируются события add и addone. Первое генерируется один раз на добавление (например, вы добавили несколько элементов с помощью push, событие вызвалось только один раз), второе генерируется один раз на каждый добавленный элемент.
При срабатывании события add, значением свойства added объекта события передается массив добавленных элементов, а при срабатывании addone, значением свойства addedItem - каждый отдельный добавленный элемент.
// обработчик запустится трижды,// так как в массив добавили три новых элементаthis.on('addone', evt => { console.log(evt.addedItem); // 1 ... 2 ... 3});this.push(1, 2, 3);
При удалении элементов действует та же логика: remove срабатывает один раз, даже если удалено несколько элементов, а событие removeone срабатывает для каждого удаленного элемента индивидуально. При генерации события remove удаленные элементы содержатся в свойстве removed объекта события, а при генерации события removeone - каждый удаленный элемент содержится в свойстве removedItem.
this.push(1, 2, 3, 4, 5);// обработчик запустится трижды,// так как из массива удалили три элементаthis.on('removeone', evt => { console.log(evt.removedItem); // 2 ... 3 ... 4});this.splice(1, 3);
При каждой модификации массива генерируется событие modify, позволяя отловить все без исключения изменения в массиве (добавление, удаление, пересортировку).
length - это обычное свойство которое можно связывать с HTML элементом или отлавливать изменения с помощью события change:length.
Например, при добавлении трех элементов с помощью метода push с тремя аргументами, генерируются следующие события: push, add, addone (трижды), modify, change:length.
Свойство Matreshka.Array#Model определяет класс элементов, которые будет содержать коллекция. Рекомендуется наследовать Model от класса Matreshka.Object или Matreshka.Array (на случай, если требуется получить коллекцию коллекций), чтобы получить возможность конвертации массива в обычный массив рекурсивно методом Matreshka.Array#toJSON.
Matreshka.Array умеет автоматически отрисовывать элементы на странице при любых модификациях массива. За подробностями обратитесь к документации Matreshka.Array#itemRenderer.
Этот метод служит для того, чтоб перехватить и трансформировать добавленные в массив элементы. Обратите внимание, метод переопределяет свойство Matreshka.Array#Model.
Вызов метода конвертирует элементы, которые уже есть в массиве, и заменяет их отрендеренные узлы; события при этом не генерируются.
Любой метод из Array.prototype с возможностью передать объект события
Ознакомившись с Matreshka.Array#METHOD становится понятно, что методы не поддерживают передачу объекта события, так как в точности повторяют синтаксис и количество аргументов встроенного Array. Синтаксис МЕТОД_ позволяет передать в обработчик события какие-нибудь данные либо установить служебные флаги, отвечающие за поведение массива после вызова метода.
Свойство определяет класс элементов, которые будет содержать коллекция
При каждом добавлении элементов в массив, встроенный обработчик проверяет, является ли добавленный элемент экземпляром Model и конвертирует его в таковой, если проверка не пройдена. Рекомендуется наследовать Model от класса Matreshka.Object или Matreshka.Array (на случай, если требуется получить коллекцию коллекций), чтоб получить возможность конвертации массива в обычный массив методом Matreshka.Array#toJSON.
Для более гибкого контроля класса элементов (например, если для одних элементов нужно использовать одну Модель, а для других - другую), используйте Matreshka.Array#mediateItem.
Model можно объявить как поле класса (Model = MyModel;), а не только как геттер. Поля класса устанавливаются после возврата из super(...), поэтому элементы, переданные в super(...), конвертируются, когда массив в следующий раз изменяет какой-нибудь метод; геттер get Model() конвертирует их сразу.
Если изменить Model позже, элементы, которые уже есть в массиве, конвертируются, а их отрендеренные узлы заменяются.
Сортирует массив по значениям свойств объектов, которые в него входят
Этот метод работает почти так же, как и метод orderBy из lodash. Он принимает ключ или массив ключей - первым аргументом, порядок (asc/desc) или массив порядков - вторым.
Метод позволяет конвертировать любой массив (или объект, подобный массиву) в экземпляр Matreshka.Array. Если ничего не передано в качестве первого аргумента, экземпляр очищается.
Если аргумент selector не задан, то коллекция будет воссоздана из элементов, входящих в контейнер ("container" или "sandbox").
При воссоздании, на каждом элементе массива генерируется событие render и вызываются методы onRender и onItemRender (см документацию), как и при обычном рендеринге.
Метод работает рекурсивно, вызывая toJSON для входящих объектов, у которых есть метод с таким именем. Для отмены рекурсии передайте false первым аргументом.
Перерисовывает DOM узлы элементов, входящих в массив
Этот метод заново рендерит элементы массива в контейнере массива. Если узел, который ассоциирован с элеменом масива уже создан, метод, вместо перерисовки с нуля, "перевставляет" его в контейнер или песочницу массива.
Метод может быть полезным на случай, когда элементы добавлены в массив перед объявлением песочницы или контейнера.
Чтоб заставить массив перерисоваться, независимо от наличия отрендеренных узлов (например, вы используете кастомный шаблонизатор в itemRenderer), передайте в метод объект со свойством forceRerender равным true.
HTML строка, селектор или функция, отвечающая за отрисовку элементов массива на странице
Свойство itemRenderer - это переопределяемое (виртуальное) свойство, которое позволяет рендерить элементы массива без дополнительного кода. При вставке нового элемента в массив, автоматически создается HTML узел. Этот узел становится песочницей (см. Matreshka#bindNode) (это поведение можно отменить, см. ниже) для вставленного элемента и встраивается в HTML контейнер, определенный в массиве.
Для краткости, в примерах к этой статье будет использоваться синтаксис class fields.
itemRenderer и Model работают и как поля класса, и как геттеры; новое значение поля itemRenderer перерендеривает элементы. Об элементах, переданных в super(...), см. Matreshka.Array#Model.
Для того, чтобы определить место, в которое будут вставляться отрисованные HTML узлы, нужно определить контейнер. Для этого следует объявить HTML песочницу для массива либо связать специальный ключ container с HTML контейнером. Подробнее о привязках и песочнице см. Matreshka#bindNode.
Пример использования песочницы в качестве контейнера:
Теперь все новосозданные узлы <li> попадут в узел .my-list
Если вы не хотите вставлять HTML узлы непосредственно в песочницу, можете связать ключ container с необходимым элементом. Такая логика нужна в том случае, если песочница не ограничена одними лишь элементами коллекции и включает в себя другие HTML узлы.
<div class="my-widget"> <h1>This is my awesome list</h1> <ul class="my-list"></ul></div>
class MyArray extends Matreshka.Array { itemRenderer = '<li>'; get Model() { return MyModel; } constructor() { super(); // определяем песочницу this.bindNode('sandbox', '.my-widget'); // определяем контейнер для HTML элементов this.bindNode('container', '.my-list'); }}
В примере выше HTML узлы будут вставляться в .my-list вместо .my-widget.
Свойство itemRenderer поддерживает несколько вариантов определения, но все они должны содержать или возвращать единственный HTML узел.
На случай, если вы выносите шаблоны для элементов на HTML страницу, itemRenderer поддерживает селектор в качестве значения. В этом случае, Matreshka.Array будет искать HTML элемент в DOM дереве и извлечет innerHTML найденого элемента. В случае, если элемент не найден, бросается исключение.
HTML текст от селектора отличается наличием символа < в строке.
Использование функции в качестве значения свойства itemRenderer может пригодиться, когда есть нужда динамически генерировать элемент для рендеринга. Функция может возвращать:
Иногда удобно объявлять рендерер внутри класса Matreshka.Array#Model, а не на уровне коллекции. Свойство renderer перекрывает значение itemRenderer, если оно задано для элемента коллекции.
В этом случае, можно вовсе не указывать itemRenderer, так как render дочернего элемента перенимает все его возможности. Синтаксис остаётся такими же: можно использовать HTML, селектор или функцию.
После того, как объект вставлен в массив, а его HTML узел уже создан, но еще не вставлен в контейнер, генерируется событие render на вставленном элементе. После его генерации можно объявить привязки свойств к HTML узлам, содержащимся внутри этого элемента.
afterrender, в свою очередь, генерируется после вставки HTML элемента в контейнер массива.
<form class="my-form"> <label> <input type="checkbox" class="my-checkbox"> <span class="text">Buy a raccoon</span> </label> <label> <input type="checkbox" class="my-checkbox"> <span class="text">Sell the raccoon</span> </label></form>
И свяжет чекбоксы с соответствующими свойствами isChecked и text.
Не забывайте, что в Matreshka.js реализована возможность отлова делегированных событий. Т. е. сам массив может отловить событие рендеринга элемента, используя имя события *@render (см. документацию к eventNames).
this.on('*@render', () => { alert('Child element is rendered');});
Отрисованный HTML узел становится песочницей для вставленного элемента, позволяя использовать селектор :sandbox и другие возможности после рендеринга. Если элемент входит сразу в несколько коллекций, установите ему свойство bindRenderedAsSandbox: false, чтобы отменить это поведение.
class MyModel extends Matreshka.Object { bindRenderedAsSandbox = false; // ...}
Для улучшения читаемости кода в одной из предыдущих версий появился виртуальный метод Matreshka.Array#onItemRender, который можно использовать вместо события render. В качестве альтернативы, у "моделей" вызывается метод onRender, так же позволяющий сделать код более "плоским" и избавиться от вложенных функций.
Взглянув на примеры использования Matreshka.Array и Matreshka.Array#itemRenderer можно обратить внимание на то, что вся логика, отвечающая за двустороннюю и одностороннюю привязку данных заключена в JavaScript коде. Но когда разрабатываешь очень простую коллекцию, не включающую в себя сложную логику, массу привязок и пр. хотелось бы иметь более краткий вариант объявления привязок. Для этого, в itemRenderer может быть передан шаблон, включающий привязки, заключенные в фигурные скобки (см. Matreshka#parseBindings).
class MyArray extends Matreshka.Array { itemRenderer = `<label> <input type="checkbox" checked="{{isChecked}}">{{text}} </label>`; // ...}const app = new MyArray();
Как видно выше, если у дочернего элемента задано свойство render, Matreshka.Array попробует его отрисовать. Для того, чтоб полностью отменить рендеринг для массива, присвойте свойству массива renderIfPossible значение false.
class MyArray extends Matreshka.Array { renderIfPossible = false; // ...}
По умолчанию, при вставке объекта в массив фреймворк попытается его отрисовать, используя itemRenderer. Это даёт преимущество в случаях, когда у вас на странице есть два или более списка, включающих в себя один и тот же объект. При изменении этого объекта, все списки моментально реагируют на изменение, обновляя DOM.
Но иногда стоит задача перемещения объекта между коллекциями, не перерисовывая его заново. Для перемещения объекта из одного массива в другой, включая его песочницу, используйте флаг moveSandbox.
Такая необходимость может возникнуть тогда, когда вы используете серверный пререндеринг (см. Matreshka.Array#restore), а шаблон подгружается динамически.
Объект, входящий в коллекцию, не обязательно должен быть экземпляром Matreshka, можно рендерить любой объект. Байндинги для таких объектов можно объявить используя статичный метод Matreshka.bindNode.
class MyArray extends Matreshka.Array { // Model не определена itemRenderer = ...; onItemRender(item) { Matreshka.bindNode(item, 'x', ':sandbox .some-node'); }}
Свойство trackBy указывает на имя свойства идентификатора объектов, входящих в массив
В случае, если клиент и сервер активно обмениваются данными (например, списком пользователей), а объекты, входящие в массив имеют уникальный ID (например, идентификатор пользователя), то перерисовка всей коллекции с нуля не имеет смысла. После того, как сервер вернул новую коллекцию, намного рациональнее проверить, есть ли в коллекции объект с таким же ID и, если объект найден, обновить его. Таким образом, не создаётся новый объект (экземпляр Matreshka.Array#Model) и не рисуется новый DOM узел.
trackBy работает только при использовании метода Matreshka.Array#recreate, так как это единственный метод "пересоздающий" коллекцию заново.
В примере ниже используется _id в качестве значения (значение может быть любым).
class MyArray extends Matreshka.Array { get trackBy() { return '_id'; } constructor() { super(); //... }}const arr = new MyArray();// добавит три объекта в массивarr.recreate([ {_id: 0, name: 'Foo'}, {_id: 1, name: 'Bar'}, {_id: 2, name: 'Baz'}]);// удалит объект с идентификатором 0// добавит объект с идентификатором 3// обновит объект с идентификатором 1, обновив name: Bar -> BarNew// обновит объект с идентификатором 2, обновив name: Baz -> BazNew// пересортирует коллекцию в соответствие с переданным даннымarr.recreate([ {_id: 1, name: 'BarNew'}, {_id: 3, name: 'Qux'}, {_id: 2, name: 'BazNew'}]);
Свойство может содержать специальное значение "$index", которое позволяет обновлять объект по индексу в коллекции.
class MyArray extends Matreshka.Array { get trackBy() { return '$index'; } constructor() { super(); //... }}const arr = new MyArray();// добавит три объекта в массивarr.recreate([ {name: 'Foo'}, {name: 'Bar'}, {name: 'Baz'}]);// обновит все три объекта новыми данными// и добавит новый объект с именем Quxarr.recreate([ {name: 'NewFoo'}, {name: 'NewBar'}, {name: 'NewBaz'}, {name: 'Qux'}]);
Пространство имен для байндеров. Из коробки содержит байндеры общего назначения (для связывания атрибутов и пр.). Этот объект можно расширять собственными свойствами, чтоб не засорять глобальное пространство имен.
Примите во внимание небольшое соглашение: каждое свойство из коллекции Matreshka.binders должно быть оформлено в виде функции (такие функции иногда называют "binder creator"), возвращающей байндер.
В этой документации свойства из Matreshka.binders используются напрямую, но для улучшения читаемости кода, рекомендуется выносить их в отдельные переменные.
const html = Matreshka.binders.html;// ...this.bindNode('x', node, html());
Либо импортировать в качестве CJS модуля:
// импорт сразу нескольких байндеровimport { binders } from 'matreshka';const { html, text, prop } = binders;
attr – Возвращает байндер, меняющий атрибут DOM элемента на значение свойства объекта
className – Возвращает байндер, который переключает имя класса DOM элемента в зависимости от значения свойства объекта. Если значение свойства нестрого равно true, имя класса добавляется, в противном случае - убирается. Логику можно изменить, передав false вторым аргументом, и, таким образом, имя класса будет добавляться, когда значение свойства нестрого равно false и наоборот.
dataset – Возвращает байндер, меняющий заданное свойство объекта dataset DOM элемента в зависимости от значения свойства объекта.
display – Возвращает байндер для одностороннего связывания, меняющий видимость DOM элемента (используя style.display), в зависимости от значения свойства объекта
existence – Возвращает байндер для одностороннего связывания, переключающий наличие элемента в DOM дереве, в зависимости от значения свойства объекта
html – Возвращает байндер, меняющий innerHTML DOM элемента в зависимости от значения свойства объекта
input – Возвращает байндер, связывающий свойство объекта с элементом input. Напрямую байндер использовать не обязательно, так как он входит в список Matreshka.defaultBinders.
output – Возвращает байндер, связывающий свойство объекта с элементом output. Напрямую байндер использовать не обязательно, так как он входит в список Matreshka.defaultBinders.
progress – Возвращает байндер, связывающий свойство объекта с элементом progress. Напрямую байндер использовать не обязательно, так как он входит в список Matreshka.defaultBinders.
prop – Возвращает байндер, меняющий свойство DOM элемента на значение свойства объекта
select – Возвращает байндер, связывающий свойство объекта с элементом select. Напрямую байндер использовать не обязательно, так как он входит в список Matreshka.defaultBinders.
style – Возвращает байндер, меняющий заданное свойство стиля DOM элемента в зависимости от значения свойства объекта.
text – Возвращает байндер, меняющий textContent (текстовое содержимое) DOM элемента в зависимости от значения свойства объекта.
textarea – Возвращает байндер, связывающий свойство объекта с элементом textarea. Напрямую байндер использовать не обязательно, так как он входит в список Matreshka.defaultBinders.
Возвращает байндер, который переключает имя класса DOM элемента в зависимости от значения свойства объекта. Если значение свойства нестрого равно true, имя класса добавляется, в противном случае - убирается. Логику можно изменить, передав false вторым аргументом, и, таким образом, имя класса будет добавляться, когда значение свойства нестрого равно false и наоборот.
Возвращает байндер для одностороннего связывания, меняющий видимость DOM элемента (используя style.display), в зависимости от значения свойства объекта
import { binders } from 'matreshka';const { existence } = binders;
Matreshka.binders.existence([bool=true]) → binder
Возвращает байндер для одностороннего связывания, переключающий наличие элемента в DOM дереве, в зависимости от значения свойства объекта
Байндер работает так же, как и Matreshka.binders.display, но вместо изменения видимости элемента, изменяется наличие элемента на странице. Байндер полезен для:
Крупных приложений: в зависимости от состояния роутера показать ту или иную страницу;
Для реализации бесконечного скроллинга;
Для других задач, где нужно спрятать элемент, но его наличие в DOM дереве не обязательно.
Возвращает байндер, связывающий свойство объекта с элементом input. Напрямую байндер использовать не обязательно, так как он входит в список Matreshka.defaultBinders.
Для текстовых полей undefined и null отображаются как пустая строка. Для radio: отмеченный переключатель устанавливает свойству своё value; снятый переключатель свойство не меняет.
Возвращает байндер, связывающий свойство объекта с элементом output. Напрямую байндер использовать не обязательно, так как он входит в список Matreshka.defaultBinders.
Возвращает байндер, связывающий свойство объекта с элементом progress. Напрямую байндер использовать не обязательно, так как он входит в список Matreshka.defaultBinders.
Возвращает байндер, связывающий свойство объекта с элементом select. Напрямую байндер использовать не обязательно, так как он входит в список Matreshka.defaultBinders.
undefined и null выбирают опцию без значения, если она есть. Если multiple равен true, свойство может быть массивом значений, одним значением или null (ничего не выбрано); значения сравниваются как строки, поэтому числа тоже работают.
Возвращает байндер, связывающий свойство объекта с элементом textarea. Напрямую байндер использовать не обязательно, так как он входит в список Matreshka.defaultBinders.
Matreshka.initRouter построен на классе Matreshka.Router. Он принимает единственный аргумент - тип роутера ("hash", "history" или произвольная строка).
По умолчанию библиотека создает два экземпляра Matreshka.Router с типами hash и history. Они находятся в Matreshka.Router.hash и Matreshka.Router.history. Роутер инициализируется лениво, поэтому, если просто подключить скрипт к странице, библиотека ничего не делает.
Для этих двух экземпляров используется паттерн «одиночка». Это значит, что при попытке создать еще один экземпляр типа hash через new Matreshka.Router('hash') вместо нового экземпляра вернется Matreshka.Router.hash. Так вся работа с URL собрана в одном месте, это хорошо для производительности и исключает коллизии. Объекты, переданные в initRouter, просто подписываются на изменения роутера нужного типа.
Свои экземпляры (не hash и не history) Matreshka.Router можно создать вручную, если нужно сгенерировать URL для дальнейшего использования. В этом случае изменения свойств не трогают hash и не вызывают pushState.
binder (байндер, привязчик) содержит всю информацию о том, как синхронизировать значение свойства с привязанным к нему DOM элементом. Для всех методов байндера контекст (this) - соответствующий DOM узел.
Имя DOM события (или список имен событий, разделенных пробелами), после срабатывания которого извлекается состояние DOM элемента и устанавливается свойство. Кроме этого, значением свойства может быть функция, которая устанавливает обработчик произвольным образом.
Функция, которая вызывается после вызова метода unbindNode. Если байндер достаточно сложен, destroy может содержать удаление логики, навешаной байндером
// для всех событийthis.on('addevent', evt => {...});// для события "someevent"this.on('addevent:someevent', evt => {...});// генерирует события "addevent" и "addevent:someevent"this.on('someevent', evt => {...});
// для всех событийthis.on('removeevent', evt => {...});// для события "someevent"this.on('removeevent:someevent', evt => {...});// генерирует события "removeevent" и "removeevent:someevent"this.off('someevent', evt => {...});
При возникновении необходимости слушать изменения во всех элементах Matreshka.Array или во всех свойствах, отвечающих за данные Matreshka.Object, вместо имени свойства можно указать звездочку "*".
Можно использовать Matreshka.js на сервере Node.js (требуется наличие jsdom, точнее, объекта window, доступного глобально) либо воспользоваться совершенно любым шаблонизатором на любой серверной платформе. Первый вариант подходит для статичной генерации HTML, второй - для страниц, которые генерируются при каждом обращении пользователя.
Задача клиентской стороны - восстановить приложение из HTML. Matreshka#bindNode извлечет состояние элемента и присвоит его свойству, а Matreshka.Array#restore восстановит состояние коллекции.
В документации часто встречается фраза "микропаттерн debounce". Это распространенный паттерн, который превращает несколько вызовов функции в течение определенного времени в один вызов. Подробнее в статье на Хабрахабр или на англоязычном ресурсе.
Приложение на базе Matreshka.js, как правило, представляет из себя один вложенный JavaScript объект, каждая ветвь которого - экземпляр Matreshka. Новые ветви приложения создаются при помощи Matreshka#instantiate, что гарантирует целостность приложения и возможность вернуть предыдущее состояние приложения или одной его ветви обычными присваиванием.
Первое: нужно установить классу этого объекта свойство bindRenderedAsSandbox со значением false. Это отключает автоматическое создание песочницы при рендеринге объекта в массиве (т. к. запрещено одному объекту иметь две песочницы).
Второе: по событию render проверить, в какой массив был вставлен объект и объявить байндинги, соответствующие массиву. Родительский массив и отрисованный элемент можно получить, обратившись к свойствам parentArray и node объекта события.
class User extends Matreshka.Object { constructor() { super(); this.bindRenderedAsSandbox = false; setInterval(() => { // изменив name изменятся и все связанные // с этим свойством элементы this.name = Math.random(); }, 5000); } onRender(evt) { const { parentArray, node } = evt; if(parentArray instanceof UsersA) { this.bindNode({ // создаём псевдо-песочницу // для синтаксического сахара в селекторах // (это не обязательно) nodeA: node, name: ':bound(nodeA) .name', email: ':bound(nodeA) .email', }); } else if(parentArray instanceof UsersB) { this.bindNode({ nodeB: node, name: ':bound(nodeB) .user-name', }); } }}
На самом деле есть много способов решения этой задачи. Например, можно объявлять байндинги на уровне массива (т. е. массив будет слушать событие render вставленных объектов) или на уровне массива привязывать только псевдо-песоницу, а на уровне объекта слушать событие bind и объявлять остальные привязки...