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)

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

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

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

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

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

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

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

Комментарии (23)


  1. HeadWithoutBrains
    08.04.2015 13:50
    +6


    1. Finom Автор
      08.04.2015 14:14
      +2

      1. 4dmonster
        08.04.2015 15:46
        +1

        Видимо авторам статей о фреймворках лучше самим добавлять эту картинку :).


        1. 4dmonster
          08.04.2015 16:17

          А вам ещё и приписывать про гипотетический метод toMatreshka (или просто to).


  1. zenden2k
    08.04.2015 16:16
    +1

    не надо было брать название которое прочно ассоциируется с другим опен-сурс проектом


    1. spmbt
      09.04.2015 01:54
      +2

      Тот — Matroska, а тут есть balalaika (DOM) уже. Осталось Event driven движок сделать (vodka, ushanka?).


  1. kiselev_dv
    08.04.2015 20:18
    +1

    Вам бы примеров побольше, например
    — простенькое дерево категорий (на генерацию хтмля)
    — взаимодействие со сторонними библиотеками (например с картами, добавляем десяток точек они появляются на карте, связываем клик по маркеру и содержимое попапа с моделью)
    — работа с hash частью url'a и историей (я понимаю что это немного за рамками библиотеки, но всеже)


    1. Finom Автор
      08.04.2015 20:42
      +1

      Спасибо, записал в список.


  1. Glebcha
    10.04.2015 16:10

    Давно присматривался и уже забыл, а тут ваша статья.

    Ни капли злой иронии, но почему я должен посмотреть Matreshka.js если мне безумно крутым и легким показался Mithril.js?

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

    Теперь классы — а почему бы не ES6 синтаксис сразу в документации, прям жирным шрифтом? IE8 идет лесом как мне кажется, уже давно и навсегда.

    А вообще побольше примеров и, желательно, посложнее и более приближенных к реальным задачам (например гриды, формы и так далее).


    1. Finom Автор
      10.04.2015 18:31

      но почему я должен посмотреть Matreshka.js если мне безумно крутым и легким показался Mithril.js?

      Все фреймворки хороши по-своему. Вы мне ничего не должны :)

      Теперь классы — а почему бы не ES6 синтаксис сразу в документации, прям жирным шрифтом?

      Когда ES6 будет использоваться повсеместно, так и сделаем. Сейчас нет в этом смысла. Многие даже не в курсе, что это такое.

      IE8 идет лесом как мне кажется, уже давно и навсегда.

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

      А вообще побольше примеров и, желательно, посложнее и более приближенных к реальным задачам (например гриды, формы и так далее).

      Полностью согласен. Но очень сложно соблюсти баланс между максимальной понятностью и функционалом примера.


      1. Glebcha
        10.04.2015 18:59

        Но вы же хотите привлечь внимание, назовите причины по которым это стоит сделать с оглядкой на упомянутый крутой фрэймворк.
        Неужели вам не хочется понять в сравнении преимущества своего детища? Я же не издеваюсь ни капли.

        Да уж я с вами не соглашусь, про ES6 знают и его хотят использовать. Многие ждали и верили, дождались и используют.

        Я уже год назад отказался от поддержки IE < 8 и не собираюсь возвращаться и извращаться. Того же и всем желаю.

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


        1. Finom Автор
          10.04.2015 19:17

  1. Finom Автор
    10.04.2015 19:17

    Но вы же хотите привлечь внимание, назовите причины по которым это стоит сделать с оглядкой на упомянутый крутой фрэймворк.
    Неужели вам не хочется понять в сравнении преимущества своего детища? Я же не издеваюсь ни капли.

    Я вряд ли когда-то буду говорить, что такой-то фреймворк плохой, а этот лучше всех. Почему стоит использовать или не использовать Матрешку, решаете вы сами. Главная задача разработчика любого свободного инструмента — дать возможность определиться, нужно ли его использовать или нет. Мне кажется, здесь с этим всё в порядке: есть сайт, есть «рекламирующие» тексты, есть документация, есть примеры (в том числе и средних размеров: TodoMVC).

    Я уже год назад отказался от поддержки IE < 8 и не собираюсь возвращаться и извращаться.

    Я и не против, но клиенты требуют.


  1. Delphinum
    13.04.2015 00:01

    Зачем же вы добавляете в ваше решение излишний (на мой взгляд) функционал? Нет желания пользоваться AngularJS только от того, что мне не нужны их фильтры да сервисы, а вот связывание хочется.

    Думаю перейти на «матрешку», но можно ли уменьшить ее вес, выпилив все, кроме связывания?


    1. Finom Автор
      13.04.2015 12:06

      Связывание тут реализуется достаточно просто. Любой программист со средним опытом может реализовать standalone функцию bindNode с помощью Object.defineProperty и addEventListener.


    1. Finom Автор
      14.04.2015 19:14

      Решил для небольшой тренеровки после обеда написать отдельную функцию bindNode.

      Вот, если вам интересно:

      window.bindNode = function bindNode( object, key, node, binder ) {
      	var value = object[ key ];
      	Object.defineProperty( object, key, {
      		get: function() {
      			return value;
      		},
      		set: function( v ) {
      			binder.setValue.call( node, v );
      		}
      	});
      	
      	node.addEventListener( binder.on, function() {
      		value = binder.getValue.call( node );
      	});
      };
      


      Функция поддерживает только один набор аргументов, аргумент node должен быть DOM нодой, нет стандартных байндеров и пр., но вы можете сами дописать, это не трудно.

      jsbin.com/mabetap/2/edit?html,js,console,output
      Введите data.x = 'Hello world' в консоли.


      1. Finom Автор
        14.04.2015 19:18

        Ну и «многие ко многим» не поддерживаются. Но это всё детали.


      1. Delphinum
        14.04.2015 22:50

        Спасибо. Возможно было бы очень удобно, если бы вы разделили фреймворк на части, чтобы можно было наращивать функционал постепенно, по мере надобности.

        Использую ваш фреймворк в связке с React. Думаю об использовании балалайки )


        1. Finom Автор
          15.04.2015 00:55

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

          Тогда фреймворк уеньшится на 20%, по моим оценкам, что очень немного, при этом не отразившись на производительности. Поэтому, я не вижу в этом смысла.

          Использую ваш фреймворк в связке с React

          Буду благодарен, если поделитесь опытом (здесь или в личке). Очень интересно.


          1. Delphinum
            15.04.2015 11:09

            Буду благодарен, если поделитесь опытом (здесь или в личке). Очень интересно.


            Постараюсь что нибудь написать специально для вас (распространять имеющиеся исходники не имею права)


          1. Delphinum
            15.04.2015 15:42

            Идея в том, чтобы позволить программисту рендерить структуру виджета с помощью React, но обновлять конкретные значения виджета через связывание.

            Пример использования
            define('components/Timer', ['react', 'matreshka'], function (React, Matreshka) {
                return React.createClass({
                    displayName: 'Timer',
            
                    // Увеличиваем значение таймера используя модель
                    tick: function () {
                        this.model.value = Number(this.model.value) + 1;
                    },
            
                    // Обнуляем таймер используя модель
                    clean: function(){
                        this.model.value = 0;
                    },
            
                    // Создаем модель с использованием Matreshka
                    initModel: function(){
                        this.model = new (Matreshka.Class({
                            'extends': Matreshka.Object,
                            constructor: function (node, value) {
                                this.value = value;
            
                                this.bindNode('value', node, {
                                    setValue: function (v) {
                                        this.innerHTML = v;
                                    }
                                });
                            }
                        }))(this.getDOMNode().lastChild, this.props.value);
                    },
            
                    // Вешаем обработчики на виджет
                    componentDidMount: function () {
                        this.initModel();
            
                        this.timer = setInterval(this.tick, 500);
            
                        this.getDOMNode().lastChild.addEventListener('click', this.clean);
                    },
            
                    // Удаляем обработчики с виджета
                    componentDidUnmount: function () {
                        clearInterval(this.timer);
            
                        this.getDOMNode().lastChild.removeEventListener('click', this.clean);
                    },
            
                    // рендерим виджет
                    render: function () {
                        return React.createElement('div', null, [
                            React.createElement('span', null, 'Timer: '),
                            React.createElement('span', {title: 'Click to reset'})
                        ]);
                    }
                });
            });
            


            1. Delphinum
              15.04.2015 16:21

              Более сложный пример. Головной элемент состоит из подэлемента, который способен изменять свою HTML структуру (благодаря React). Тот, в свою очередь, состоит из еще более мелких элементов, которые могут изменять свое значение (благодаря Matreshka).

              Пример использования
              var todo = React.createClass({
                      inc: function(){
                          this.model.value = Number(this.model.value) + 1;
                      },
              
                      componentDidMount: function(){
                          this.model = new (Matreshka.Class({
                              'extends': Matreshka.Object,
                              constructor: function (node, value) {
                                  this.value = value;
              
                                  this.bindNode('value', node, {
                                      setValue: function (v) {
                                          this.innerHTML = v;
                                      }
                                  });
                              }
                          }))(React.findDOMNode(this.refs.data), this.props.value);
              
                          React.findDOMNode(this.refs.button).addEventListener('click', this.inc);
                      },
              
                      render: function(){
                          return React.createElement('li', null, [
                              React.createElement('span', {ref: 'data'}, this.props.value),
                              React.createElement('input', {type: 'button', value: 'inc', ref: 'button'})
                          ]);
                      }
                  });
              
                  var todoList = React.createClass({
                      getInitialState: function(){
                        return {items: this.props.items};
                      },
              
                      addLi: function(text){
                          var newState = this.state.items;
                          newState.push(React.createElement(todo, {value: text}));
                          this.setState(newState);
                      },
              
                      render: function(){
                          return React.createElement('ul', null, this.state.items)
                      }
                  });
              
                  var todoApp = React.createClass({
                      addTodo: function(){
                          this.refs.todoList.addLi(React.findDOMNode(this.refs.text).value);
                          React.findDOMNode(this.refs.text).value = '';
                      },
              
                      componentDidMount: function(){
                          React.findDOMNode(this.refs.button).addEventListener('click', this.addTodo);
                      },
              
                      componentDidUnmount: function(){},
              
                      render: function(){
                          return React.createElement('div', null, [
                              React.createElement(todoList, {ref: 'todoList', items: []}),
                              React.createElement('input', {type: 'text', ref: 'text'}),
                              React.createElement('input', {type: 'button', value: 'Add', ref: 'button'})
                          ]);
                      }
                  });
              


              1. Finom Автор
                15.04.2015 19:17

                Круто, спасибо. Небольшое замечание по Матрешке: если вам нужно свойство с определенным типом, юзайте mediate.

                this.mediate('value', Number);
                

                Тогда вместо

                this.model.value = Number(this.model.value) + 1;
                

                Можно писать

                this.model.value++;