...

понедельник, 4 августа 2014 г.

Matreshka.js v0.2



Всем привет. Представляю очередное обновление фреймворка Matreshka.js до версии 0.2. Напомню: Матрешка — фреймворк общего назначения с окрытым исходным кодом, в идеологию которого положено доминирование данных над внешним видом: вы задаёте правила, как интерфейс должен синхронизированться с данными, затем работаете исключительно с данными, кроме случаев, когда событие интерфейса не касается данных (например, щелчек по кнопке или сабмит формы, сами по себе, не меняют данные, а запускают функцию, которая, в свою очередь, работает с данными).
Пример
Матрешка позволяет довольно просто связать данные и элементы представления (например, свойство объекта и значение поля ввода), не заботясь о дальнейшей синхронизации данных и представления. Например, самая простая привязка выглядит так:

<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 = 'Привет';




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




Ссылка на сайт Матрешки. Ссылка на github репозиторий.

Поддержка AMD




Матрешка теперь моддерживает спецификацию определения асинхронных модулей, Asynchronous Module Definition. Другими словами, Матрешка совместима с библиотеками, типа requirejs. Это значит, что теперь можно писать тру-код, не гадящий в глобальное пространство имен. Поддерживается два типа подключения: запрос именованного модуля и запрос безымянного модуля.

Именованные модули:



requirejs.config({
paths: {
xclass: 'path/to/matreshka',
matreshka: 'path/to/matreshka'
}
});
require(['xclass', 'matreshka'], function(Class, MK) {
return Class({
'extends': MK
//...
});
});




Но это, скорее, побочный эффыект использования новой файловой структуры проекта. А рекомендованный способ — запрос безымянного модуля:

require(['path/to/matreshka'], function( MK ) {
return MK.Class({
'extends': MK
// ...
});
});




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



Метод Matreshka#addDependency: новое имя и дополнительные фичи




1. Метод addDependence был переименован в addDependency по подсказке хабраюзера buriy (спасибо ему), старый метод помечен, как «устаревший».

2. Метод теперь поддерживает обещанную возможность добавления зависимости от свойств других классов. Синтаксис второго аргумента таков: [ инстанс, "ключ", инстанс, "ключ", инстанс, "ключ" ... ] — массив, с нечетными элементами — экземплярами классов, четными — ключами этих экземпляров, от которых и зависит искомое свойство. Взгляните на пример:

this.addDependency( 'a', [
anotherInstance1, 'b',
this, 'c',
anotherInstance2, 'd'
], function( b, c, d ) {
return b + c + d;
});




Здесь свойство "a" зависит от свойства "b" объекта anotherInstance1, от свойства "d" объекта anotherInstance2 и от собственного свойства "c". Старый синтаксис по-прежнему работает:

this.addDependency( 'a', 'b c', function( b, c ) {
return b + c;
});




3. Безопасные зависимости. Этот пункт никак не отражается на синтаксисе: начиная с этого релиза метод избегает бесконечного цикла при неправильном использовании addDependency. Представьте себе ситуацию, когда свойство "a" зависит от свойства "b", свойство "b" зависит от свойства "c", а свойство "c", в свою очередь, зависит от "a". Абстрактная иллюстрация к примеру:

this.addDependency( 'a', 'b', function( b ) {
return b * 2;
});

this.addDependency( 'b', 'c', function( c ) {
return c * 3;
});

this.addDependency( 'c', 'a', function( a ) {
return a / 5;
});




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

this.addDependency( 'p', 'a b', function( a, b ){
return (a + b) * 2;
});

this.addDependency( 'a', 'p b', function( p, b ){
return p/2 - b;
});

this.addDependency( 'b', 'p a', function( p, a ){
return p/2 - a;
});


Статичный метод Matreshka.procrastinate




Представьте себе следующую ситуацию (взята из моей практики). У вас есть форма с некими текстовыми полями: чекбосами и пр. Когда меняется значение одного из элементов формы, приложение должно отправить запрос на сервер, который, в свою очередь, возвращает данные для рендеринга трёх графиков. Рисование графиков — дело тяжелое для процессора и на слабом компьютере занимает полсекунды (Highcharts он такой). Теперь представьте пользователя, которому скучно и он решил многократно кликнуть на чекбокс. Что произойдет? Отправится куча запросов, вернется куча ответов, которые так же многократно отрисуют график. Что обычно делают в таком случае? Отменяют запрос на сервер. Спрашивается: зачем было этот запрос посылать, если можно было дождаться, пока тот угомонится? :)

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



var doSomethingHeavy = function( i ) {
console.log( 'Ok', i );
};

var procrastinateSomethingHeavy = MK.procrastinate( doSomethingHeavy );

for( var i = 0; i < 100; i++ ) {
procrastinateSomethingHeavy( i );
}

// >> Ok 100




Код функции (на случай, если вы захотите использовать её вне Матрешки):

var procrastinate =function ( f, d, thisArg ) {
var timeout;
if( typeof d !== 'number' ) {
thisArg = d;
d = 0;
}
return function() {
var args = arguments,
_this = this;
clearTimeout( timeout );
timeout = setTimeout( function() {
f.apply( thisArg || _this, args );
}, d || 0 );
};
};




Метод, кроме «прокрастинирующей» функции, принимает задержку, и контекст в качестве аргументов. Задержка отвечает за то, на сколько миллисикунд будет отложен реальный вызов функции при очередной попытке её вызова.

А вот пример случая, когда функция никогда не будет вызвана (для лучшего понимания).



var procrastinateSomethingHeavy = MK.procrastinate( function() {
console.log( 'Ok' );
}, 1000 );

setInterval( function() {
procrastinateSomethingHeavy();
}, 500 ); // интервал меньше задержки


Новый ключ привязчика initialize




Привязчик (binder) — третий аргумент метода Matreshka#bindElement. Если вы помните, это объект, состоящий из трех свойств: on (по какому DOM событию обновить свойство), getValue (как извлечь значение свойства из элемента), setValue (как установить значение свойства элементу). Подробнее вот здесь (кстати, все статьи о Матрешке обновляются каждый релиз и являются актуальным материалом). Теперь появился еще одно опциональное свойство initialize.

initialize — функция, запускающаяся во время привязки, а точнее, до неё. Задача функци — подсластить код. Взгляните на пример из первой статьи:



Во-первых, перед привязкой объявим слайдер:



<div class="slider"></div>



$( ".slider" ).slider({ min: 0, max: 100 });




Во-вторых объявляем экземпляр Матрешки:

var mk = new Matreshka();




Дальше вызываем привязку:

mk.bindElement( 'x', '.slider', {
on: 'slide', // событие, по которому из элемента извлекается значение
getValue: function() {
return $( this ).slider( 'option', 'value' ); // как вытащить значение из элемента (см. документацию jQuery ui.slider)?
},
setValue: function( v ) {
$( this ).slider( 'option', 'value', v ); // как установить значение для элемента (см. документацию jQuery ui.slider)?
}
});




Код несколько избыточен: мы дважды обращаемся к элементу с классом slider (сначала, применяя плагин, затем привязывая элемент). Теперь этого можно избежать:

var mk = new Matreshka();

mk.bindElement( 'x', '.slider', {
initialize: function() {
$( this ).slider({ min: 0, max: 100 });
},
on: 'slide',
getValue: function() {
return $( this ).slider( 'option', 'value' );
},
setValue: function( v ) {
$( this ).slider( 'option', 'value', v );
}
});


Метод Matreshka#defineSetter




Этот новый метод, как не трудно догадаться, определяет сеттер для свойства.

this.defineSetter( 'x', function( value ) {
return alert( value );
});




При использовании метода нужно помнить, что он перетирает встроенный сеттер свойства (если он был) и события изменения свойства не будут работать.

this.x = 1;

this.on( 'change:x', function( evt ) { // обраьотчик, который не сработает из-за перетертого сеттера
alert( 'x is changed to ' + evt.value );
});

this.defineSetter( 'x', function() {
// ...
});

this.x = 2;


Новый синтаксис для имен событий: добавление обработчикоов событий для свойств и элементов коллекции




Сожалуй, самое важное в этом релизе — возможность добавить обработчик события на внутреннее содержимое экземпляра.
Событие "ключ@имя_события"



Теперь можно добавить обработчик для свойства внутри любого класса, унаследованного от Матрешки (в том числе, для MK.Object и MK.Array), при условии, если значением свойства является экземпляр Матрешки. Взгляните на пример:

var mk = new MK;
mk.on( 'x@yeah', function() {
alert( 'yeah' );
});

mk.x = new MK;
mk.x.trigger( 'yeah' );




Обратите внимание, что порядок определения свойства и навешивания обработчика не важен: вы можете сперва добавить обработчик события, а, затем, объявить свойство. Причем, если значение свойства меняется, то обработчик срабатывает только для нового значения, а для старого обработчик удаляется.
Событие "@имя_события" для MK.Object



Такое имя события позволяет добавить обработчик для JSON ключа экземпляра MK.Object (что такое JSON ключ, или ключ, отвечающий за данные, смотрите в статье об MK.Object).

var mkObject = new MK.Object;

mkObject.on( '@yeah', function() {
alert( 'yeah' );
});

mkObject.jset( 'x', new MK );

mkObject.x.trigger( 'yeah' );




Порядок объявления свойства и обработчика событий так же не важен.
Событие "@имя_события" для MK.Array



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

var mkArray = new MK.Array;

mkArray.on( '@yeah', function() {
alert( 'yeah' );
});

mkArray.push( new MK );

mkArray[ 0 ].trigger( 'yeah' );




Эти три изменения не ограничтваются только лишь прослушкой события "yeah", можно с уверенностью слушать и другие события, например, "change:свойство"

this.on( 'x@change:y', function() { /* ... */ } );
this.on( '@change:y', function() { /* ... */ } );




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

{
a: [{
b: { c: { e: 1 } }
}, {
b: { d: { e: 2 } }
}]
}




Для того, чтоб докапаться до изменений свойства "e", можно добавить такой обработчик:

this.on( 'a@@b@@change:e', function() { /* ... */ } );


Метод Matreshka#$bound




У Матрешки есть два метода, возвращающие привязанные элементы: Matreshka#bound, который возвращет первый привязанный элемент или null и Matreshka#boundAll, который возвращает коллекцию привязанных элементов. Здесь могут возникнуть проблемы у новичков, работающих с jQuery и не знакомых с VanillaJS в понимании термина «коллекция» и привыкших к знау доллара. Поэтому, во фреймворк был добавлен метод $bound делающий совершенно то же самое, что и Matreshka#boundAll.

this.bindElement( 'a', '#x, #y' );
this.$bound( 'a' ).animate( /* ... */ ); // применяем любой jQuery метод


Другие изменения




Matreshka.useAs$ вместо usejQuery и useBalalaika



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

Примеры использования:



MK.useAs$( jQuery );
MK.useAs$( jQuery.noConflict() );
MK.useAs$( Zepto );
MK.useAs$( MK.$b ); // Балалайка




Следствием этого изменения стало то, что Матрешка, загружаясь, использует библиотеку знак-доллара, если такая есть и имеет определенные методы, вместо использования только лишь jQuery. Какие именно методы, можете узнать в исходном коде одного из файлов проекта.
Метод xclass.same



Небольшое изменение, добавляющее ситаксический сахар в классы (см. статью о наседовании). Часто, создавая класс, конструктору этого класса требуется, всего лишь, вызвать конструктор родителя в собственном контексте:

var MyClass = Class({
'extends': AnotherClass,
constructor: function() {
AnotherClass.call( this, arguments );
},
someNewMethod: function() { /* ... */ }
});




Теперь то же самое можно сделать более кратко:

var MyClass = Class({
'extends': AnotherClass,
constructor: AnotherClass.same(),
someNewMethod: function() { /* ... */ }
});


Добавление обработчиков DOM событий (например, "click::x") до того, как элемент был привязан



У Матрешки есть возможность навешивать обработчики событий на приязанные элементы с помощью метода Matreshka#on:

this.bindElement( 'x', '.my-element' );
this.on( 'click::x', function() {
alert( '.my-element is clicked' );
});




Проблема в том, что нельзя было добавить обработчик DOM собтия до того, как элемент был привязан. Приходилось извращаться ожиданием события bind и добавлением обработчика по наступлению этого события:

this.on( 'bind:x', function() {
this.on( 'click::x', function() {
alert( '.my-element is clicked' );
});
});

this.bindElement( 'x', '.my-element' );




Теперь порядок привязки/добавления DOM события не важен:

this.on( 'click::x', function() {
alert( '.my-element is clicked' );
});
this.bindElement( 'x', '.my-element' );


Исправленные ошибки/рефакторинг





  • Matreshka.Array#initializeSmartArray (документация к методу в работе) теперь возвращает this

  • Matreshka.Array#createFrom принимает undefined в качестве аргумента

  • Изменены случаи, когда срабатывает событие "modify" для класса Matreshka.Array

  • Матрешка теперь вызывает событие "delete" при удалении свойства вместо "remove" потому что у Matreshka.Array есть событие с таким же именем, но вызываемое в другом случае

  • Если [].forEach не существует, генерируется ошибка с предложением подсключить es5-shim

  • Исправлен баг в парсере Балалайки

  • Исправлен баг в методе Matreshka#once, теперь обработчик может быть удален с помощью метода Matreshka#off

  • Теперь триада eventName + eventHandler + context может быть добавленна только раз на один экземпляр

  • Исправил баг в функции Class (splice vs slice)

  • Рефакторинг методов Matreshka#on and Matreshka#off

  • Небольшой рефакторинг Matreshka#trigger и MK#set


Что дальше?




1. В следующей статье я ознакомлю вас с реализацией TodoMVC. Статья, уже готова, но требует редактирования. Реализация тоже готова, но для нее допиливается документация.

2. После этого планируется большая статья о MK.Array, заменяющая предыдущую. Там я расскажу подробне о методах, о том, как рендерятся элементы массива, о «модели» и о том, как передавать опции в методы массивов.

3. Версия 0.3 с кучей интересных изменений, которые уже тестируется. Как обычно, будет статья.

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


Всем добра!


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий