Matreshka.js: От простого к простому

от автора

image

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

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

Пост является краткой компиляцией первых четырех статей о Матрешке с актуальными ссылками на документацию, обновленными методами и синтаксисом.

Напомню, Матрешка — front-end фреймворк, соблюдающий несколько важных принципов, среди которых

  • Никакой логики в HTML
  • Минимум сущностей
  • Произвольная архитектура

Матрешка реализует простой синтаксис двустороннего связывания данных и активно использует акцессоры (геттеры и сеттеры).

this.bindNode( 'x', 'input.my-node' ); this.on( 'change:x', function() {     alert( this.x ); }); this.x = 'Wow!'; 

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

1. Hello World!

Давайте начнем с самого простого: подключим нужные скрипты на страницу и свяжем свойство x с двумя узлами на странице: с полем ввода (двусторонняя привязка) и обычным <div> (односторонняя привязка). При изменении свойства выведем сообщение в консоль.

Сначала создадим HTML файл.

<!DOCTYPE html> <html>     <head>         <title>Моё первое приложение на базе Матрешки</title>     </head>     <body>         <input type="text" class="my-input">         <div class="my-output"></div>         <script src="http://cdn.jsdelivr.net/matreshka/latest/matreshka.min.js"></script>         <script src="js/app.js"></script>     </body> </html> 

Теперь создадим JS файл js/app.js со следующим содержимым:

var Application = Class({     'extends': Matreshka,     constructor: function() {          // связываем свойство x и текстовое поле         this.bindNode( 'x', '.my-input' );          // связываем свойство x и блок с классом my-output         this.bindNode( 'x', '.my-output', {             setValue: function( v ) {                 this.innerHTML = v;             }         });          // если свойство х изменилось, сообщаем об этом в консоли         this.on( 'change:x', function() {             console.log( 'x изменен на ' + this.x );         });     } });  var app = new Application(); 

Теперь откройте консоль и введите:

app.x = 'Wow!'; 

Как вы можете заметить, произошло три вещи:

  1. Обновилось значение поля ввода
  2. Обновилось HTML содержимое блока
  3. В консоль вывелась информация о том, что x поменяли

При вводе текста в текстовое поле:

  1. Обновилось свойство x
  2. Обновилось HTML содержимое блока
  3. В консоль вывелась информация о том, что x поменяли

Как видите, не нужно вручную отлавливать событие ввода в поле текста; при изменении значения свойства не нужно вручную устанавливать значения HTML узлам; не нужно объявлять дескриптор самостоятельно.

Не забывайте, что это работает даже в Internet Explorer 8.

Живой пример

2. Форма авторизации. Знакомимся с «моделью» (Matreshka.Object)

Следующий пример — реализация формы авторизации на сайте. У нас есть два текстовых поля: логин и пароль. Есть два чекбокса: «показать пароль» и «запомнить меня». Есть одна кнопка: «войти». Скажем, что валидация формы пройдена тогда, когда длина логина не меньше 4 символов, а длина пароля не меньше 5 символов.

image

Немного теории: Matreshka.Object играет роль класса, создающего объекты типа ключ-значение. В каждом экземпляре класса можно отделить свойства, отвечающие заданные (то что будет передано не сервер, например) от других свойств (то, что серверу не нужно, но определяет поведение приложения). В данном случае, логин, пароль и “запомнить меня” являются данными, которые мы отправляем на сервер, а свойство, говорящее о том, валидна ли форма — нет.

Подробная и актуальная информация об этом классе находится в документации.

Итак, создадим класс, который наследуется от Matreshka.Object (или более кратко: MK.Object).

var LoginForm = Class({ 	'extends': MK.Object, 	constructor: function () { 		// ... 	} }); 

Так как “приложение” очень небольшое, всю логику можно разместить в конструкторе класса.

Перво-наперво, объявим данные по умолчанию.

.jset({ 	userName: '', 	password: '', 	rememberMe: true }) 

Метод jset не только устанавливает значения, но и объявляет свойства, отвечающие за данные. Т. е. userName, password и rememberMe должны быть переданы на сервер (в этом примере просто выведем JSON на экран).

Объявляем свойство, isValid, которое зависит от свойств userName и password. При изменении любого из этих свойств (из кода, консоли или с помощью привязанного элемента), свойство isValid тоже изменится.

.linkProps( 'isValid', 'userName password', function( userName, password ) { 	return userName.length >= 4 && password.length >= 5; }) 

isValid будет равно true, если длина имени пользователя не меньше четырех, а длина пароля — не меньше пяти. Метод linkProps — это еще одна крутая возможность фреймворка. Одни свойства могут зависеть от других, другие от третьих, в третьи вообще от свойств другого объекта. При этом, вы защищены от цикличных ссылок. Метод прекращает работу если встречается с опасными зависимостями.

Теперь связываем свойства объекта и элементы на странице. Первым делом объявляем песочницу. Песочница нужна для того, чтоб ограничить влияние экземпляра одним элементом на странице и избежать конфликтов (например, если на странице есть два элемента с одним и тем же классом). Затем привязываем остальные элементы.

Метод bindNode отвечает за двустороннее связывание данных.

Он принимает четыре аргумента: ключ свойства, HTML элемент (или селектор), правило привязки и объект события (какие данные должны быть переданны в обработчик bind и bind:KEY). Первые два аргумента — обязательны.

Правило привязки (байндер, “привязчик”) — это объект состоящий из трех основных свойств (четвертое — initialize, но нам оно пока не интересно), отвечающих за то как связать свойство экземпляра класса с HTML узлом.

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

Первое, что нужно узнать — это то, какое событие HTML узла говорит нам о том, что значение элемента изменилось. В случае с текстовым полем — это событие keyup.

on: 'keyup' 

Второе — как извлечь значение элемента. В данном случае, нужно вытащить значение value элемента. Функция будет вызываться каждый раз, после срабатывания события keyup, а возвращаемое значение присваиваться соответствующему свойству объекта (в данном случае, свойству x).

getValue: function() { 	return this.value; } 

Используя jQuery, код будет выглядеть так:

getValue: function() { 	return $( this ).val(); } 

Третье — как установить новое значение элементу. Функция вызывается каждый раз, когда меняется значение свойства x.

setValue: function( v ) { 	this.value = v; } 

Или c jQuery:

setValue: function( v ) { 	$( this ).val( v ); } 

Теперь, собираем всё вместе:

this.bindNode( 'x', '.my-input', { 	on: 'keyup', 	getValue: function() { 		return this.value; 	}, 	setValue: function( v ) { 		this.value = v; 	} }); 

Или, в случае использования jQuery:

this.bindNode( 'x', '.my-input', { 	on: 'keyup', 	getValue: function() { 		return $( this ).val(); 	}, 	setValue: function( v ) { 		$( this ).val( v ); 	} }); 

Обратите внимание, в Матрешке реализована привязка всех без исключения HTML5 полей ввода без указания третьего аргумента. Поэтому, вместо громоздкой писанины, можно было бы сделать так:

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

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

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

Более подробную информацию вы найдете в документации к методу bindNode.

// альтернативный синтаксис метода позволяет передать объект ключ-элемент в качестве первого аргумента, // что несколько уменьшает количество кода .bindNode({ 	sandbox: '.login-form', 	userName: ':sandbox .user-name', 	password: ':sandbox .password', 	showPassword: ':sandbox .show-password', 	rememberMe: ':sandbox .remember-me' }) 

Как видите, для остальных элементов используется нестандартный селектор :sandbox, ссылающийся на песочницу (на элемент с классом .login-form). В данном случае это не обязательно, так как страница содержит только нашу форму. В ином случае, если на странице есть несколько форм или других виджетов, настоятельно рекомендуется ограничивать выбираемые элементы песочницей.

Затем, связываем кнопку, отвечающую за отправку формы, и свойство isValid. Когда isValid равно true, добавляем элементу класс «disabled», когда false — убираем. Это пример одностороннего привязчика, т. е. значение свойства объекта влияет на состояние HTML элемента, но не наоборот.

.bindNode( 'isValid', ':sandbox .submit', { 	setValue: function( v ) { 		$( this ).toggleClass( 'disabled', !v ); 	} }) 

Вместо такой записи можно использовать более краткую:

.bindNode( 'isValid', ':sandbox .submit', MK.binders.className( '!disabled' ) ) 

См. документацию к объекту binders.

Связываем поле с паролем и свойство showPassword (“показать пароль”) и меняем тип инпута в зависимости от значения свойства.

.bindNode( 'showPassword', ':bound(password)', { 	getValue: null, 	setValue: function( v ) { 		this.type = v ? 'text' : 'password'; 	} }) 

getValue: null означает то, что мы переопределяем стандартное поведение фреймворка при привязке элементов формы.

Добавляем событие отправки формы.

.on( 'submit::sandbox', function(evt) { 	this.login(); 	evt.preventDefault(); }) 

submit — обычное, произвольное DOM или jQuery событие, sandbox — это наша форма (.login-form). Такое событие и ключ должны быть разделены двоеточием. Это синтаксический сахар DOM событий, т. е. событие можно навешать любым другим способом, в том числе, и используя addEventListener:

this.bound( 'sandbox' ).addEventListener( 'submit', function() { … } ); 

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

Последний штрих — метод login. Для примера, метод выводит на экран результирующий объект, если форма валидна. В реальном приложении, содержимым функции, очевидно, должен быть ajax запрос на сервер.

login: function () { 	if( this.isValid ) { 		alert( JSON.stringify( this.toJSON() ) ); 	} 	return this; } 

В самом конце создаём экземпляр класса.

var loginForm = new LoginForm();  

Можете снова открыть консоль и изменить свойства вручную:

loginForm.userName = 'Chuck Norris'; loginForm.password = 'roundhouse_kick'; loginForm.showPassword = true; 

Круто?

Ссылка на живой пример

3. Список пользователей. Разбираемся с коллекциями (Matreshka.Array)

С данными вида ключ-значения разобрались. Пришло время рассмотреть коллекции. Скажем, задача звучит так: вывести список неких людей в виде таблицы.

image

Чтобы не усложнять пример, поместим подготовленные данные в переменную data.

var data = [{ 		name: 'Ida T. Heath', 		email: 'ida@dayrep.com', 		phone: '507-879-9766' 	}, { 		name: 'Robert C. Burkhardt', 		email: 'rburkhardt@teleworm.us', 		phone: '321-252-5698' 	}, { 		name: 'Gerald S. Reaves', 		email: 'gsr@rhyta.com', 		phone: '765-431-5347' }]; 

(имена и телефоны получены с помощью генератора случайных данных)

Для начала, как обычно, создаём HTML разметку.

<table class="users"> 	<thead> 		<th>Name</th> 		<th>Email</th> 		<th>Phone</th> 	</thead> 	<tbody><!-- здесь будет список пользователей --></tbody> </table> 

Объявим коллекцию Users, которая наследуется от Matreshka.Array (более кратко — MK.Array).

var Users = Class({ 	'extends': MK.Array, }); 

Укажем свойство itemRenderer, которое отвечает за то как элементы массива будут рендериться на странице.

itemRenderer: '#user_template', 

В данном случае, указан селектор в качестве значения, ссылающийся на шаблон в HTML коде.

<script type="text/html" id="user_template"> 	<tr> 		<td class="name"></td> 		<td class="email"></td> 		<td class="phone"></td> 	</tr> </script> 

Свойство itemRenderer может принимать и другие значения, в том числе, функцию или HTML строку.

И укажем значение свойства Model, определяя класс элементов, содержащихся в коллекции (такой синтаксис должен быть знаком пользователям Backbone).

Model: User, 

Класс User мы создадим немного позже, для начала определим конструктор новосозданного класса коллекции.

constructor: function( data ) { 	this 		.bindNode( 'sandbox', '.users' ) 		.bindNode( 'container', ':sandbox tbody' ) 		.recreate( data ) 	; } 

При создании экземпляра класса

  • Связываются свойство sandbox и элемент '.users' создавая песочницу (границы влияния класса на HTML).
  • Связываются свойство container и элемент ':sandbox tbody', определяя HTML узел, куда будут вставляться отрисованные элементы массива.
  • Добавляем переданные данные в массив методом recreate.

Теперь объявляем “Модель”: класс User, который наследуется от уже знакомого нам Matreshka.Object.

var User = Class({ 	'extends': MK.Object, 	constructor: function( data ) {} }); 

Устанавливаем данные, переданные в конструктор методом jset.

this.jset( data ); 

Затем, дожидаемся события render, которое срабатывает тогда, когда соответствующий HTML элемент был создан, но еще не вставлен на страницу. В обработчике привязываем соответствующие свойства соответствующим HTML элементам. Когда значение свойства изменится, innerHTML заданного элемента тоже поменяется.

this.on( 'render', function() { 	this 		.bindNode({ 			name: ':sandbox .name', 			email: ':sandbox .email', 			phone: ':sandbox .phone' 		}, { 			setValue: function( v ) { 				this.innerHTML = v; 			} 		}) 	; }) 

В конце создадим экземпляр класса Users, передав данные в качестве аргумента

var users = new Users( data ); 

Всё. При обновлении страницы вы увидите таблицу со списком юзеров.

Ссылка на живой пример

Теперь откройте консоль и напишите:

users.push({ 	name: 'Gene L. Bailey', 	email: 'bailey@rhyta.com', 	phone: '562-657-0985' }); 

Как видите, в таблицу добавился новый элемент. А теперь вызовите

users.reverse(); 

Или любой другой метод массива (sort, splice, pop…). MK.Array, кроме собственных методов, содержит все без исключения методы стандартного JavaScript массива. Затем,

users[0].name = 'Vasily Pupkin'; users[1].email = 'mail@example.com' 

Как видите, вам не нужно вручную следить за изменениями в коллекции, фреймворк самостоятельно ловит изменения данных и меняет DOM. Это невероятно удобно.

Не забывайте, что MK.Array поддерживает собственный набор событий. Вы можете отлавливать любое изменение в коллекции: добавление, удаление, пересортировку элементов методом on.

users.on( 'addone', function( evt ) {   console.log( evt.added.name ); });  users.push({   name: 'Clint A. Barnes' }) 

(выведет в консоль имя добавленного пользователя)

В качестве еще одного примера коллекции можете рассмотреть реализацию простейшего поисковика музыки, основанного на Soundcloud API.

Будет ли подгрузка шаблонов из файлов?

Вчера получил письмо с таким вопросом и решил опубликовать ответ.

Пока такой возможности из коробки не планируется. Шаблоны могут подгружаются с помощью AMD:

define([ 	'matreshka', 	'text!templates/template.html' ], function( MK, TEMPLATE ) { 	return MK.Class({ 		'extends': MK.Array, 		itemRenderer: TEMPLATE 		// ... 	}) }); 

Или в случае использования Babel:

import MK from 'matreshka'; import TEMPLATE from 'text!templates/template.html';  export default class extends MK.Array { 	itemRenderer() { 		return TEMPLATE; 	} 	// ... } 

При отсутствии AMD, можно запросить шаблон любым способом (например, в конструкторе), а затем вызвать метод rerender. Этот метод как раз и создан для случаев, когда itemRenderer устанавливается динамически.

var MyClass = Class({ 	'extends': MK.Array, 	constructor: function() { 		// произвольная функция для получения шаблона 		ajax( 'templates/template.html', function( TEMPLATE ) { 			this.itemRenderer = TEMPLATE; 			this.rerender(); 		}.bind( this ) ); 	} }); 

4. TodoMVC

image

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

Вывод

Матрешка — простой фреймворк, решающий своими простыми функциями огромное поле задач. При этом, не требуется описывать байндинги в HTML коде, об обработчиках «знает» только JavaScript код, а структура приложения может быть совершенно произвольной. Матрешка не ограничивает творчество разработчика, являясь, скорее, библиотекой, чем фреймворком.

Новые ресурсы:
Twitter
Gitter (en)
Gitter (ru)

Спасибо всем тем, кто сообщал об опечатках на сайте. Всем добра!

Выглядит просто?

Проголосовало 47 человек. Воздержалось 25 человек.

Собираетесь ли попробовать Матрешку в следующем проекте?

Проголосовало 52 человека. Воздержалось 26 человек.

Дайте оценку тексту документации к Матрешке

Проголосовало 28 человек. Воздержалось 37 человек.

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

ссылка на оригинал статьи http://habrahabr.ru/post/254889/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *