Pull to refresh
0
Matreshka.js
JavaScript фреймворк

(Архив) Matreshka.js v0.1

Reading time 9 min
Views 9.9K
Статья устарела. См. актуальную историю версий.


(Все предыдущие статьи обновлены до актуального состояния)
Репозиторий

Сайт (там же и документация)

Matreshka LogoВсем привет. Прошло 5 месяцев после последней публикации серии статей о Матрешке. С тех пор исправлен небольшой ряд найденных ошибок, появилось несколько удобных фич, в том числе и под влияниев ваших комментариев, проект обрел спонсора в лице Shooju, получил логотип и нормальный, не-бутстраповский сайт.



Напомню, Матрешка - фреймворк общего назначения, в котором значимость данных доминирует над внешним видом, и интерфейс автоматически обновляется, когда обновляются данные.
Матрешка позволяет довольно просто связать данные и элементы представления (например, свойство объекта и значение поля ввода), не заботясь о дальнейшей синхронизации данных и представления. Например, самая простая привязка выглядит так:
<select class="my-select">
	<option>1</option>
	<option>2</option>
	<option>3</option>
</select>

Создаем экземпляр:
var mk = new Matreshka();

Связываем свойство x с элементом .my-select:
mk.bindElement( 'x', '.my-select' );

Меняем данные
mk.x = 2;

После того, как мы присвоим свойству x другое значение, остояние элемента изменися соответствующим образом.
Взгляните на живой пример

Другой важной чертой матрешки являются события (в том числе и кастомные). Например, Матрешка умеет отлавливать изменение значения свойства:
mk.on( 'change:x', function( evt ) {
	alert( 'x изменен на ' + evt.value );
});

Код выведет "x изменен на Привет":
mk.x = 'Привет';

Подробнее об этих и других фичах смотрите по ссылкам выше.


Немного об оформлении статьи
Я буду использовать jsdoc синтаксис для обозначения методов и свойств, в частности — решетку (#) например, MK#addDependence значит, что я говорю о методе addDependence, экземпляра класса MK.


Самое вкусное


Зависимости (метод MK#addDependence)


Этот крутейший метод позволяет установить зависимость одних данных от других. Первый аргумент — ключ зависимого свойства, второй — массив ключей, от которых зависит свойство (или строка с ключами, перечисляемыми через пробел), третий — функция-обработчик, которая должна возвращать новое значение для свойства. Это эдакая замена геттеру, с той разницей, что геттер вызывается каждый раз, когда вы получаете свойство, а addDependence вычисляет значение свойства заранее, при изменении данных, от которых зависит свойство. С геттером нужно работать очень аккуратно, так как относительно тяжелые вычисления могут сильно повлиять на производительность вашего кода. «Зависимости», в свою очередь, по ресурсоемкости ничем не отличаются от обычной обработки событий изменения данных и являются, по сути, синтаксическим сахаром над ними. Кроме этого, метод является еще одним шагом к самодокументирующемуся коду.

Скажем, нам нужно, чтобы свойство f всегда являлось суммой свойств a, b, c, d, e.
Вот так выглядит код, построенный на чистых событиях:
this.on( 'change:a change:b change:c change:d change:e', function() {
	this.f = this.a + this.b + this.c + this.d + this.e;
});

Теперь сравните его с таким:
this.addDependence( 'f', 'a b c d e', function() {
	return this.a + this.b + this.c + this.d + this.e;
});

Или даже с таким:
this.addDependence( 'f', 'a b c d e', function( a, b, c, d, e ) {
	return a + b + c + d + e;
});


Во-первых, нам приходится проделывать меньше телодвижений (не нужно кучи 'change:')
Во-вторых, по имени метода и аргументам нам хорошо понятно, для чего созданы соответствующие строки кода. Переводя на человеческий язык, первый способ можно озвучить так: «при изменении свойств a, b, c, d, e делать что-то», а второй так: «добавить зависимость свойства f от свойств a, b, c, d, e». Чувствуете разницу?
В-третьих, если одно из свойств, от которых зависит другое свойство будет изменено с флагом silent, первый вариант не сработает.

Например, есть задача вычисления периметра.

Вариант 1, на событиях:
this.on( 'change:a change:b', function() {
	this.p = ( this.a + this.b ) * 2;
});

Вариант 2, с помощью зависимостей:
// Вариант 2
this.addDependence( 'p', 'a b', function() {
	return ( this.a + this.b ) * 2;
});

Теперь, если вызовем:
this.set({
	a: 2,
	b: 3
}, {
	silent: true
});
… то в первом варианте p не изменится, во втором — изменится.

Обратите внимание если вы навесили обработчик события на изменение p, и одно из свойств, от которых зависит p, изменилось с флагом silent, то, как и предполагается, обработчик изменения p не будет вызван.
this.on( 'change:p', function() { /* ... */ } );
this.set( 'a', 12, { silent: true }); // для войства "p" изменение тоже будет "тихим"

В следующей версии планируется добавить зависимость свойства от данных, находящихся в других классах (часто встречающаяся задача в приложениях, где данные доминируют над внешним видом). Это реализовано уже сейчас, но не документированно из-за ограничений синтаксиса языка. Предполагается, что зависимость от других классов будет выглядеть так:
this.addDependence( 'a', [
	instance1, 'b c d',
	instance2, 'e f g',
	this, 'h i j'
], function() { /* ... */ });

Где нечетный элемент массива из второго аргумента является экземпляром, четный — списком ключей. Выглядит специфически, буду рад другим вариантам.

На странице с примерами можете посмотреть работу метода вживую.

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

Медиаторы (посредники)


Метод MK#setMediator


Довольно часто встрачается задача валидации и конвертации данных. Скажем, в вашем классе есть свойство a, которое всегда должно быть строкой и ничем иначе. Давайте попробуем решить эту задачу, используя стандартный инструментарий:
// обработчик-конвертер
this.on( 'change:a', function() {
	if( typeof this.a !== 'string' ) {
		this.a = String( this.a );
	}
});

// какоий-нибудь обработчик1
this.on( 'change:a', function() {
	if( typeof this.a === 'string' ) {
		/* ... */
	}
});

// какоий-нибудь обработчик2
this.on( 'change:a change:b', function() {
	if( typeof this.a === 'string' ) {
		/* ... */
	}
});

// присваиваем число
this.a = 123;

Понимаете, что здесь происходит? Первый обработчик конвертирует a в строку, а последние два обработчика вынуждены проверять, является ли a строкой, так как обработчик-конвертер запускает все обработчики (в том числе и самого себя) заново. Скитаясь в поисках решения, типа события beforechange:%ключ%, было решено ввести новое понятие во фреймворк — «посредник».

Посредник (или медиатор) меняет значение свойства до того, как сработает какое-либо событие, связанное с изменением этого свойства.

Синтаксис метода MK#setMediator прост: первым аргументом передается ключ, для которого нужно установить медиатор, второй аргумент — функция, которая должна возвращать новое значение свойства. Альтернативный синтаксис: в метод передается объект ключ-медиатор для случая, если вы хотите навесить сразу несколько медиаторов на класс.

Например, свойство a всегда должно быть строкой, а свойство b всегда должно быть целым числом (или NaN)
this.setMediator( 'a', function( value ) {
	return String( value );
});

this.setMediator( 'b', function( value ) {
	return parseInt( value );
});

А для тех, кто знает Javascript хорошо:
this.setMediator( 'a', String );
this.setMediator( 'b', parseInt );

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

Метод мегаудобен и крут, в том числе, когда сервер принимает какой-нибудь определенный тип значения. Медиатор может быть только один. Следующий вызов MK#setMediator с тем же свойством перекроет старый медиатор. Для того, чтоб удалить «посредника», вместо функции можно передать null.

Взгляните на живой пример со страницы с примерами.
mk.setMediator({
	percentageValue: function( v ) {
		return v > 100 ? 100 : v < 0 ? 0 : v;
	},
	stringValue: String,
	integerValue: parseInt
});

Мы установили медиаторы для трех свойств. Первое — процентное свойство: значение свойства может быть от 0 до 100, то что выходит за границы этого диапазона, автоматически преобразуется в валидное значение (если меньше 0, то значение становится 0, если больше 100, то значение становится 100). Второе значение — строковое, свойство должно быть всегда строкой. Третье всегда должно быть целым числом (или NaN). Развивая мысль, можно создать свойство, которое всегда true или false, можно создать свойство, которое всегда будет экземпляром какого-нибудь класса…

Метод MK.Array#setItemMediator


Есть еще один вид медиатора: медиатор элемента массива. При установке такого медиатора, он преобразует каждый добавленный элемент так, как вы хотите. Взгляните на пример из документации:
var mkArray = new MK.Array( 1, 2, 3, 4, 5 );
mkArray.setItemMediator( function( value ) {
	return String( value );
});
mkArray.push( 6, 7 );
mkArray.unshift( true, {} );

console.log( mkArray.toJSON() ); // [ "true", "[object Object]", "1", "2", "3", "4", "5", "6", "7" ]

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

Не забудьте, что медиатор элемента массива може быть единственным на весь класс. А удалить медиатор можно, установив его в null.
mkArray.setItemMediator( null );

Кстати, пример выше можно исправить для продвинутых программистов:
mkArray.setItemMediator( String );

Круто?

«По многочисленным заявкам»


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

1. MK.Array#pop и MK.Array#shift возвращают удаленный элемент, вместо «себя». Starfall: Комментарий к статье, winbackgo: Комментарий к статье
2. Дефолтные биндеры для input[type="text"] и textarea теперь слушают событие 'paste', а не только 'keyup'. safron: Коментарий к статье
3. Дефолтные биндеры для input[type="checkbox"] и input[type="radio"] теперь слушают событие 'keyup'. Это значит, что с этими элементами можно работать с клавиатуры при привязки данных к ним (то же комментарий).

Balalaika

Balalaika


Кроме этого, было решено убрать жесткую зависимость от jQuery. На мой взгляд, jQuery — замечательная библиотека, но теряющая актуальность в новых браузерах. Теперь, если на странице отсутствует jQuery, её заменяет мини библотека, которую я обозвал «Балалайкой» (обратите внимание: только если отсутствует; если jQuery подключен, то по-прежнему используется jQuery).

Балалайка наследует Array.prototype, поэтому разработчику доступны все методы, которуе есть у массива, плюс jQuery совместимые методы для работы с классами (addClass, removeClass, hasClass), событиями (on, off), парсингом HTML (parseHTML) и другие.

Чтобы использовать Балалайку напрямую, используется глобальная переменная $b:
$b( 'div' ).forEach( function(){ /* ... */ } );
$b( '.blah-blah', context ).is( 'div' );
$b( 'input[type="text"]' ).on( 'keydown', handler );


(Планируется написать отдельный пост о Балалайке)

banzalik: Коментарий с пожеланием
jMas: Еще один комментарий

Другие нововведения


Метод MK#select


Выбирает первый попавшийся элемент, соответствующий селектору внутри привязанного к this (ключ "__this__", см. предыдущие статьи). Метод создан для упрощенной работы с отдельными элементами, а не с коллекциями элементов.
this.select( '.blah-blah' );


Метод MK#selectAll


Выбирает все элементы, соответствующие селектору внутри привязанного к this. Делает то же самое, что и метод $ (знак доллара). MK#selectAll создан для полноты набора методов: если есть метод «выбрать» (MK#select), значит должен быть метод «выбрать все».
this.selectAll( '.blah-blah' );
// то же самое, что и
this.$( '.blah-blah' );


Метод MK.Array#pull


Удаляет и возвращает элемент с заданным индексом.
var x = this.pull( 3 );

Является синтаксическим сахаром над splice.
var x = this.splice( 3, 1 )[ 0 ];


Свойства isMK, isMKArray и isMKObject


Свойства, которые всегда true в экземплярах соответствующих классов
var mkObject = new MK.Object();
alert( mkObject.isMK && mkObject.isMKObject ); // true


Фиксы


Кроме этого, было исправлено несколько ошибок. Вот список исправлений:


Переименованные методы и свойства


Следуя рекомендациям semver.org, устаревшие методы и свойства не будут удалены до релиза 1.0, но в консоль будут выводиться предупреждения и просьбы использовать новые методы.


«Умный» массив


В MK.Array был влит плагин MK.DOMArray, который был упомянут в статье об MK.Array. То есть функционал, который отражала гифка «для привлечения внимания» работает из коробки. Напомню, что плагин MK.DOMArray меняет DOM автоматически, когда массив менялся (добавление, удаление, сортировка...).

Взгляните на пример с сайта Матрешки. Более подробное описание умного массива планируется несколько позже.

«Дорожная карта»


  • Реализовать заменяемое свойство Model (которое будет похоже на model из Backbone). Эта фича будет являться синтаксическим сахаром над медиатором элемента массива.
  • Ленивая инициализация. Сейчас, при наследовании, нужно всегда вызывать метод initMK. Не кошерно.
  • Переписать движок событий. За основу, возможно, будет взят DOM интерфейс EventTarget.
  • Обновить метод MK#addDependence для зависимостей от других экземпляров классов (о чем было написано выше).
  • Оптимизировать код для минификатора.
  • Исправить тексты на сайте. Сайт, как вы видите, на английском языке, и в тексте есть ошибки. Вы можете помочь исправить ошибки, используя сочетание Ctrl+Enter после выделения текста, в котором есть ошибка (чтоб не делать пул реквестов). Я буду очень благодарен.


В версии 1.0 (которая планируется, примерно, через год) планируется, во-первых, удалить устаревшие методы, во вторых убрать поддержку восьмого Осла. Все котята интерета будут рады, когда никто больше не будет поддерживать ИЕ8.

В завершение


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

Еще одной целью Матрешки — это сделать разработчика, использующего фреймворк, «богом данных», который полностью, на все 100% контролирует то, что происходит в модели, конечно, не заботясь об интерфейсе. Например, планируется реализовать всплытие события изменения данных по дереву объектов и массивов, и это будет очень круто. Дальше — больше…

То пространство, которое предоставляет ядро Матрешки, базирующееся на акцессорах (геттерах и сеттерах), дает широчайшее поле для программистского творчества. Согласитесь, идея посредников и зависимостей лежала на поверхности. Скажем, у инпута свойство value всегда является строкой, чтоб бы мы туда не поместили, свойство valueAsNumber всегда число, которое зависит от строкового значения…

Спасибо, что прочли (или проскроллили) пост до конца. Всем лучей добра.
Only registered users can participate in poll. Log in, please.
Используете ли вы Матрешку?
3.28% Да, использую (безусловно буду использовать) 8
41.8% Идеи интересные, но нужно пробовать 102
32.38% Интересно, но стремно. Буду следить за дальшейшим развитием проекта 79
22.54% Не буду даже пробовать. Фреймворк не имеет будущего 55
244 users voted. 114 users abstained.
Tags:
Hubs:
+34
Comments 5
Comments Comments 5

Articles

Information

Website
matreshka.io
Registered
Founded
Employees
Unknown
Location
Украина