Для сторінок, у яких HTML уже готовий: серверний рендеринг, віджети, старі проєкти, сторінка з одного HTML-файлу. Підключіть один тег <script> і прив’яжіть властивості об’єкта до елементів просто в JavaScript — розмітку змінювати не потрібно. Проєкт розвивається з 2013 року; версія 3.0 — це один пакет з ES-модулями та типами TypeScript, без залежностей.
Розмір:matreshka.min.js важить 62,1 кБ у мініфікованому вигляді, 20,1 кБ зі стисненням gzip і 18,0 кБ з brotli — так CDN віддає його сучасним браузерам. matreshka.core.min.js, те саме без роутера й парсера форм, важить 16,9 кБ з brotli.
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 дуже просто.
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 або не існує
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');
І розширюється синтаксис можливих імен подій:
this.bindNode('myKey', '.my-element');// Відловити клік на елементі .my-elementthis.on('click::myKey', () => {...});// Відловити клік на елементі .my-element .my-inner-elementthis.on('click::myKey(.my-inner-element)', () => {...});
Якщо елемент не знайдений, кидається виняток Binding error: node is missing for key "KEY" (мініфіковані файли кидають коротке Matreshka error binding:node_missing (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.
Для ознайомлення з важливими тонкощами роботи bindNode інформація нижче обов'язкова до ознайомлення. При цьому, імена прапорів запам'ятовувати не обов'язково.
Якщо в якості key передати строку, що містить точку, то така строка буде інтерпрітірована як шлях до властивості у вкладеному об'єкті. Matreshka.js буде слухати зміни в усьому дереві, розриваючи зв'язок для старих гілок, і створюючи його для нових.
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); }});
Створює залежність значення однієї властивості від значень інших
Метод 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" буде змінений через секунду
this.calc('greeting', 'name', name => `Hello, ${name}!`);this.name = 'World';// ... in a momentalert(this.greeting); // 'Hello, World!'
Обчислення периметра прямокутника по двох сторонах (і сторін по периметру)
this.a = 3;this.b = 4;this .calc('p', ['a', 'b'], (a, b) => (a + b) * 2) .calc('a', ['p', 'b'], (p, b) => p / 2 - b) .calc('b', ['p', 'a'], (p, a) => p / 2 - a);alert(this.p); // 14this.on('change: p', () => { // "периметр змінений і дорівнює 18" console.log(`периметр змінений і дорівнює ${this.p}`);});this.a = 5;
Метод створює і фіксує екземпляр класу в якості значення заданогї властивості. При спробі перевизначити властивість, замість самого перевизначення, екземпляр оновлюється. Таким чином досягається цілісність програми, зробленої на базі Matreshka.js.
Екземпляр класу створюється під час запуску методу instantiate. Першим аргументом конструктора класу стає поточне значення властивості. У конструкторі класу необхідно передбачити те, що в нього потрапить або undefined (якщо властивість не містила до цього ніяких даних), або об'єкт, з яким треба щось зробити (наприклад, розширити екземпляр класу властивостями об'єкта).
На ділі це виглядає просто: ви створюєте звичайний клас, який майже завжди приймає якісь дані, які потрібно обробити (наприклад, використовувати їх в методі Matreshka.Object#setData).
При спробі привласнити властивості інше значення, внутрішній механізм методу instantiate, замість присвоювання, робить наступне:
Якщо вказана функція updateCallback, метод запускає його з двома аргументами: поточним значенням властивості і даними, які код намагається привласнити.
Якщо заданий клас успадкований від Matreshka.Object, екземпляр оновлюється новими даними, використовуючи метод Matreshka.Object#setData з прапором replaceData=true.
Якщо не вказана функція updateCallback і якщо класі не успадкований від Matreshka.Object або Matreshka.Array, екземпляр розширюється властивостями об'єкта, який код намагається привласнити.
Особливістю методу є відсутність обмежень на джерело класу. Класом може виступати будь-яка функція-конструктор. яка ініціалізується за допомогою оператора new, а не тільки спадкоємці Matreshka.
Функція, викликається при кожній спробі присвоїти чергові дані властивості, що дозволяє кастомізувати логіку поновлення екземпляра класу новими даними. Функція приймає два аргументи: поточне значення властивості (екземпляр класу) і дані, які намагаються привласнити.
class MyClass { // ...}// ...this.instantiate('x', MyClass);// намагаємося привласнити властивості інше значенняthis.x = { a: 42 };// this.x як і раніше екземпляр класу MyClassalert(this.x instanceof MyClass); // truealert(this.x.a); // 42
Отримання батьківского об'єкту та імені властивості. Крім даних (перший аргумент), в конструктор створюваного класу, передається посилання на об'єкт, що викликав 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}}">
Прив'язка атрибутів.
<!--Значення атрибута href буде залежативід значень властивостей "category" і "someObject.page"(одностороння прив'язка)JS: this.category = 'matreshka'; this.someObject = { page: 42 };--><a href="http://example.com/{{category}}/{{someObject.page}}">A link</a><!--Результат:<a href="http://example.com/matreshka/42">A link</a>-->
Якщо фігурні дужки не влаштовують, поміняйте їх на щось інше, використовуючи Matreshka.parserBrackets
У разі, якщо ви розробляєте велику форму зі стандартними 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").
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 – клас (точніше, конструктор класу)
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.
Додатє обробник події, що викликається лише однин раз за певний проміжок часу
Цей статичний метод працює так само, як і Matreshka#onDebounce та всі його варіації, але приймає в якості першого аргументу будь-який JavaScript об'єкт.
Парсить DOM дерево, оголошуючи прив'язки властивостей, взятих в подвійні фігурні дужки
Цей статичний метод працює так само, як і Matreshka#parseBindings та всі його варіації, але приймає в якості першого аргументу будь-який JavaScript об'єкт.
Повертає:$nodes – Колекція оброблених DOM вузлів, передана в функцію як аргумент
Повертає елементи з пісочниці, відповідні селектору
Цей статичний метод працює так само, як і Matreshka#selectAll та всі його варіації, але приймає в якості першого аргументу будь-який JavaScript об'єкт.
Тільки масиви та екземпляри 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#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, в об'єкт події, то інші властивості будуть вилучені зі списку властивостей, що відповідають за дані.
В іншому, метод працює так само, як і Matreshka#set.
this.setData('a', 1).setData('b', 2);// Присвоювання властивості 'c' трійку,// але не додаємо ключ 'c' в список ключів, що відповідають за даніthis.set('c', 3);this.each((value, key) => { console.log(key, value);});// виводить 'a' 1 і 'b' 2console.log(this.keys()); // виводить ['a', 'b']console.log(this.toJSON()); // виводить { a: 1, b: 2 }
Після використання методу setData з властивістю можна працювати, як із звичайною властивістю
Якщо передати прапор 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.
Властивість 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; // ...}
За замовчуванням, при вставці об'єкта в масив Matreshka.js спробує його змалювати, використовуючи 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 і оголошувати інші прив'язки...