Ractive.js — бриллиантовый век web-разработки

Как утверждает сама команда разработчиков, Ractive.js — это библиотека для разработки динамичных web интерфейсов, благодаря которой в мире web-разработки наступит расцвет: всем выдадут бонусы в 100%, холивары «кто круче» отступят в сторону, а разработчики, которые пишут интерактивные, динамичные сайты наконец то перестанут покрываться сединой и материться.

Короче, наступит бриллиантовый век веб-разработки.

Начиная очередной проект, прежде чем начать писать Backbone код (фу-фу-фу), решил применить это чудо в проекте (бриллианты!). А так как погуглив похабрив я понял, что на хабре всего одна статья о Ractive.js, нужно устранить эту несправедливость и заодно написать о том, правда ли нам всем свалится вагон счастья и будет ли вообще кто-то доволен. Ведь пообещать «диамантовый век» — это одно (каждые 4 года из телеков слышим), а сделать — совсем другое.

Под катом рассмотрю, что такое и как работает Ractive.js, и подробно распишу продакшн задачу с полной реализацией и описанием, чем это всё грозит уже всем нам.

Вначале что это за зверь


Если кратко (на самом деле, очень кратко, но идею уловите, а подробности придут с кодом) и по сути: Ractive.js до ужаса прост и состоит из двух половинок:

  • Темплейты (читай «вьюшка»), в которых вы очень декларативно описываете, как ваша программа\компонент должна выглядеть.
  • Данные — собственно данные, которые нужно представить во вьюшке и то, как на них влияет внешний мир (взаимодействие пользователя и\или сетевых запросов).


Соответственно, после того, как вы описали темплейт, данные и куда это все это рендарить (обычно id DOM элемента на страничке),
Ractive.js обеспечивает (причем абсолютно бескорыстно и без вашего участия) двухстороннюю связь между этими данными и темплейтом.
Т.е., как только данные меняются, тут же реактивненько меняется ваша вьюшка, которая соответствует этим данным, добовляются нужные и удаляются устаревшие DOM елементы. Ну и в обратную сторону:
как только пользователь чем-то в ваше приложение потыкал — меняются данные.
И так по кругу. И все очень реактивненько.

Вы спросите: «А в чем же радость?»

А радость в том, что вы пишите короткий, понятный, декларативный код без миллионов ивентов (которые как обычно летают взад и вперед, а мы пытаемся их обсервить, биндить и т.д.).
А главное — нет никаких манипуляций с DOM! Мы не создаем новые элементы… мы не удаляем их из DOM…
мы фактически никогда не ищем какой-то элемент при помощи $(selector) — всё всегда под рукой.
Ractive выстраивает паралельный DOM точно так же как это делает React и производит только точечные манипуляции с DOM.
Никаких лишних движений, и поэтому работает очень быстро. Кстати чем Ractive лучше\хуже React — тема для отдельной статьи.

Всё просто: поменяли данные — поменялось отображение. Это общая идея библиотечки.

Теперь посмотрим на реализацию, реальный код и подробности как это работает. Но сначала сформируем ТЗ к тому, что мы хотим написать.

Практика


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

Итак, требования:

  • Пользователь должен иметь возможность оставить многострочный текст в виде комментария.
  • Комменты должны работать без перезагрузки страницы (интерактивчик как мы все любим).
  • Комменты должны быть древовидными (можно ответить на другой коммент и создать тем самым ветку).
  • Новый коммент должен появляться с красивенькой анимацией.
  • Пользователи могут удалить свой собственный коммент.


Собственно что бы сразу понять что и как мы будем строить вот на ссылка на jsfiddle с конечным результатом.

Templates

Ractive.js для описание UI использует популярный язык темлпейтов {{mustache}}. Только они его чуть-чуть подхачили и расширили.
Не буду описывать тут mustache, о нем можно почитать здесь кто не знаком.

Опишу только особенности, которые внёс Ractive:

  • Ввели переменную this. В блоке она соответствует данному объекту. К примеру, если вы пишите
    {{#user}}
        <div>this.name</div>
    {{/user}}
    
    , то если
    var user = {name:'Вася'}
    
    получится
    <div>Вася</div>
    
  • Если блок — это массив, то this будет равняться элементу массива.
  • А также вы можете выполнять любой js код внутри {{ }}. Так, если
    var user = { 
        age:15,
        name:'Ольга'
    } 
    

    и темплейт:
    {{#user}} 
        <div class="{{this.age < 18+1*Math.random() ? 'illegal' : 'legal'}}">this.name </div>
    {{/user}}
    

    то на выходе получим предсказуемый результат
    <div class='illegal'>Ольга</div>
    

  • Проксирование DOM Events: если писать в темплейте
    <div on-eventName='callbackName'>
    

    , где eventName — это название стандартного DOM события ( click, submit и т.д.), то при наступлении события, ractive может подписаться на такой ивент в виде
    ractive.on('callbackName',function(e){});
    


Есть еще отличия, но сейчас не до них.
Дальше всё просто: каждый инстанс Ractive имеет проперти data с данными, которые должны быть отображены в темплейте.
Он засовывает data в темплейт и строит нужную нам HTML структуру, причём каждому объекту из data соответствует блок html из темплейта.
Поэтому при изменении data Ractive точно знает, что нужно добавить, а что удалить из реального DOM, и поэтому работает очень быстро, производя точечные замены в DOM.
Также, поскольку каждому кусочку DOM теперь соответствует кусок данных в data, все ивенты, которые проксируются черех on-eventName (к примеру on-click), сопровождаются ссылкой на данные из data как context. К примеру, если у меня есть такой темплейт:

{{#user}}
      <-- здесь this=user -->
      <div on-click='alert_username'>{{this.username}}</div>
{{/user}}

то обработчик события alert_username может выглядеть так:
function(e){
        var user = e.context;
        alert(user.username);
}


Надеюсь, что на реальном примере станет все понятней.

Далее — наша имплементация темплейта для нашей задачи, но сначала опишем структуру наших данных (комментов) в data:

ractive.data = {'replys':[
                        {
                         md_text:"Тут текст комента",
                         date:10240912834091,//Веремя комента nix
                         //Автор
                         user:{
                             username:'Василий Петровичь',
                             image:'http://link-roga-serega-avatar.ru'
                         },
                         //Массив с ответами
                         replys:[{
                                md_text:"Тут ну очень интересный комента",
                                date:10240912834091,
                                replys:[...]},
                                {...}]
                         },
                        {
                         md_text:"Тут текст другого комента",
                         date:10240912834091,//Веремя комента nix
                         user:{
                            username:'Василий Петровичь'
                         },
          ]};

Сам темплейт с комментариями:
{{#top_reply}}
        <!--Мы показываем форму добавления комментария сверху и снизу. Но когда комментариев нет, только снизу-->
        {{#if replys.length > 0}}
            <!--Если есть черновик - покажем форму-->
            {{#if this.reply_draft}}
                <!--это развернется в темплейт который находится между блоками comment_form - темплейт формы введения комментария.-->
                {{>comment_form}}
            {{else}}
                <!--Если нет, то покажем кнопку 'Добавить комментарий' которая раскроет форму -->
                <!--По нажатию выстрелит событие reply и e.context = this; this = top_reply            -->
                <button class="add_comment" on-click="reply">Добавить комментарий</button>
            {{/if}}
        {{/if}}
    {{/top_reply}}

    <!--Для каждого комментария верхнего уровня рендерим partial комментария смотри ниже-->
    {{#replys}}
        {{>comment}}
    {{/replys}}

    <!--Нижняя форма ответа-->
    {{#bottom_reply}}
        {{>comment_form}}
    {{/bottom_reply}}


    <!-- {{>comment}} -->
    <!--Темплейт комментария -->
    <!--Важно понять что поскольку этот partial показываем из блока replys - this будет равняться текущему комментарию-->
    <article id="{{this.id}}" class="comment" intro="scroll_to:{go:{{this.is_new}}}">
        <header>
            <!--Тут мы выставляем пользовательский аватар либо дефолтную картинку если у пользователя нет аватара-->
            <img class="avatar" src=""/>

            <span class="author">{{this.user.username}}</span>
            <!-- Можно использовать функции. Здесь мы используем moment что бы отформатировать время. Ссылку на функцию мы передали через блок data-->
            <time pubdate datetime={{moment(parseInt(date)).format()}}>
                {{moment(parseInt(date)).fromNow()}}
            </time>
            <!--Только если автор комента - это текущий пользователь мы показываем кнопку удалить-->
            {{#if user.id === current_user.id}}
                <button class="delete" on-click="delete" disabled="{{deleting}}">Удалить</button>
            {{/if}}
        </header>

        <!--Вставляем текст комментария отформатировав его-->
        {{{marked(md_text)}}}

        <footer>
            <!--если мы не набираем коммент покажем кнопку ответить-->
            {{^this.reply_draft}}
                <button on-click="reply">Ответить</button>
            {{/this.reply_draft}}
        </footer>

        <!-- форма ввода комментария-->
        {{#if this.reply_draft}}
            {{>comment_form}}
        {{/if}}

        <!--А теперь мы рекурсивно показываем ответы-->
        {{#this.replys}}
            {{>comment}}
        {{/this.replys}}

    </article>
    <!-- {{/comment}} -->


    <!-- {{>comment_form}} -->
      <article class="comment">
        <header>
            <img class="avatar" src=""/>
            <span class="author">{{current_user.username}}</span>
        </header>
        <!--при сабмите формы мы передаем ивент save-->
        <!--при чем this а соответсвенно и е.context будет указывать на текущий комментарий.-->
        <form on-submit="save" on-reset="cancel_reply">
            <!--Он автоматом связывает и синхронизует значения input с полем текста черновика. -->
            <!--так если вы что то вбиваете в textarea это сразу появляется в this.reply_draft.text-->
            <textarea value="{{this.reply_draft.text}}" placeholder="Ваш комментарий"></textarea>
            <!--отключаем форму во время сохранения комментария-->
            <button type="submit" disabled="{{this.loading}}">Комментировать</button>
            <button type="reset">Отменить</button>
        </form>
    </article>
    <!-- {{/comment_form}} -->


Данные

Теперь опишем, как темплейт связаны с данными.
Модель тут очень простая, поэтому долго останавливаться не буду: JS класс Comments с пропертями типа текст, дата публикации, пользователь, а также возможностью коммент сохранить на сервер и удалить с него.
Также присутствует метод скачивания коммента с сервера, как и обещал ничего интересного.
function Comment(id, md_text, user, domain, date, reply_to) {
    	if (!md_text || !user || !domain || !date) {
    		throw new Error('md_text, user, domain and date is required');
    	}
    	this.id = id;
    	this.md_text = md_text;
    	this.user = user;
    	this.date = date;
    	this.reply_to = reply_to ? reply_to.id || reply_to : null;
    	this.domain = domain;
    	this.replys = [];
};
Comment.prototype.destroy = function () {
        //ajax запрос на удаление коммента
};
Comment.prototype.save = function () {
    	//ajax запрос на сохранение комента
};

Comment.fetch = function (domain, options) {
    	//скачиваем все комменты за раз.
};

Теперь имплементация Ractive компоненты:
var Comments = Ractive.extend({
    	//$(selector) Элемента куда Ractive будет рендарить этот компонент
    	el: '#comments',
    	//id tag <script> в котором содержится темлейт для этого компонента ( подробней в разделе темплейтов)
    	template: '#comments_template',
        init       : function (options) {
    		var self = this;
    		//Домен куда сохранять на сервере коменты.
    		var domain = options.comments_domain;
    		//Текущий пользователь.
    		var current_user = options.current_user;
    		if (!current_user) {
    			throw new Error('options.domain and options.current_user is required');
    		}
    		//И так для начала качнем комменты с сервера.
    		Comment
    			.fetch(domain)
    			.then(function (comments) {

    				//Разложим коменты по айдишникам что бы потом меньше бегать по списку.
    				var comments_by_id = _.indexBy(comments, 'id');
    				//Сортируем по дате
    				comments = _.sortBy(comments, 'date');
    				//Так как коменты имеет древовидную форму
    				//Раскладываем ответы в массив replys отцовского комента, а так же оставляем в массиве только комменты верхненго уровня.
    				comments = _.filter(comments, function (comment) {
    					if (comment.reply_to && comments_by_id[comment.reply_to]) {
    						var parent = comments_by_id[comment.reply_to];
    						parent.replys.push(comment);
    					}
    					return comment.reply_to === null
    				});
    				//reative.set(prop_name,value) сетит данные в data, ractive.get(prop_name) берет данные из data.
    				//Таким образом рактив узнает что что то поменялось и нужно обновить вьюшку.
    				//Если мы сделаем self.data.replys = comments то ничего автоматом не обновиться.
    				self.set('replys', comments);
    			});

    		//Ractive предоставляет стандартный механизм ивентов.
    		//Events можно слушать при помощи ractive.on('event_name',callback); или .on({prop_name:callback});
    		//Events в основном используются чтобы слушать действия пользователя... к примеру этот подлец ткнул в кнопку на страничке.
    		self.on({
    			//Пользователь хочет сохранить комментарий.
    			//е тут самое интересное. это внутренний объект ractive и он состоит из
    			//e.node - html DOM откуда прилетел ивент.
    			//e.original - оригинальный ивент DOM (удобно для e.original.preventDefault());
    			//e.keypath - в этом месте чуть сложнее. Каждой модельке в ractive.data он присваивает путь. К примеру если у нас data выглядит так:
    			//data:{ comments:['text1','text2'] }; то путь к строке 'text2' будет описанн в ввиде 'comments.1'
    			//Ractive внутренне строит паралельный DOM и когда рендерит темплейт, каждому DOM элементу или сегменту соответствует keypath модели.
    			//ну и e.context - это собственно данные которые находятся на e.keypath ( ractive.get(e.keypath) );
    			save        : function (e) {
    				e.original.preventDefault();
    				//save вызываеться из формы коммента. Соответственно, если форма комента находится в корне, а не под каким-то коментом - то этот комментарий не являеться ответом.
    				var reply_to = e.context.id ? e.context.id : null;
    				//Собственно сам текст комента.
    				var reply = e.context.reply_draft;
    				//Собираем комент
    				var new_comment = new Comment(void 0, reply.text, current_user, domain, moment().valueOf(), reply_to);
    				//Отключем форму сохранения
    				self.set(e.keypath + '.loading', true);
    				//Сохраняем коммент
    				new_comment.save()
    					.then(function (comment) {
                            //типа сетевой запрос
                            setTimeout(function(){
                                //Прекращаем загрузку, очищаем форму.
    						self.set(e.keypath + '.loading', false);
    						self.set(e.keypath + '.reply_draft', false);
    						var comments = reply_to ? self.get(e.keypath + '.replys') : self.get('replys');
    						//добавляем коммент в массив ответов
    						comments.push(comment);
    						//Все. Данные уже отображены, больше ничего делать не нужно.
    						//Причем радость в том что Ractive будет рендерить только то что нужно.
    						//Если что-то добавить в массив он добавит в дом только один элемент в нужное место, а все остальное останется как было.
                            },600);
    					})
    					.fail(function (err) {
    						self.set(e.keypath + '.loading', false);
    					});
    			},
    			delete      : function (e) {
    				//Удаление комента.
    				//Мы не удаляем коменты а только затираем текст.
    				//Поэтому тут по сути так же как сохранение.
    				self.set(e.keypath + '.deleting', true);
    				e
    					.context
    					.destroy()
    					.then(function (comment) {
    						self.set(e.keypath + '.md_text', comment.md_text);
    						self.set(e.keypath + '.deleting', false);
    					})
    					.fail(function () {
    						self.set(e.keypath + '.deleting', false);
    						//TODO: show erorr;
    					});
    			},
    			reply       : function (e) {
    				//e.keypath тут путь текущего коммента в темплейте.
    				//Это пользователь княпнул и решил ответить на комментарий.
    				//Создаем объект черновика коммента. Наверное сразу нужно было создавать тут объект Comments... но это будет в v2.0
    				self.set(e.keypath + '.reply_draft', {});
    			},
    			cancel_reply: function (e) {
    				//Передумал чувак коментить...
    				//Трем его черновик
    				self.set(e.keypath + '.reply_draft', false);
    			}
    		});

    	},
    	//data содержить данные и функции которые будут доступны в темплейтах.
    	data: {
    		bottom_reply: {}, //Про top&bottom_reply чуть позже, это небольшой хак.
    		top_reply: {},
    		marked: marked, //data так же может хранить не только объекты, но и функции которые будут доступны в темплейтах.
    		moment: moment //хелпер функции для работы со временем.
    	}
    });

Анимации

В любой элемент можно добавить атрибут intro\outro для DOM елемента: , и каждый раз при добавлении\удалении элемента из DOM Ractive будет запускать указанную анимацию.

Анимации описаны функцией, которая как параметр принимает объект:

t.node — DOM элемент, который нужно анимировать.
t.isIntro — вставляется или удаляется элемент.
t.complete – функцию, которую нужно вызвать после того, как вы все заанимировали и всё закончилось. Ractive заресетит все стили элемента в начальное состояние.

Собственно функция анимации наших комментов выглядит так:

//Ractive можно заставить выполнять анимации на каждом добавлении или удалении в\из DOM элементов.
    	transitions: {
        		scroll_to: function (t, param) {
        			//Если элемент вставляется и дано добро от разработчика (param.go === true)
        			if (t.isIntro && param.go) {
        				//Скроллим к элементу который вставляется в дом.
        				//Единственное место где используеться $
        				var element = $(t.node);
        				var offsetTop = element.offset().top - element.outerHeight();
        				$('html, body').animate({scrollTop: offsetTop}, 500, function () {
        					//Потом мигнем новым коментарием
        					t.setStyle('background-color', 'yellow');
        					t.animateStyle('background-color', 'transparent', {
        						duration: 1500,
        						easing  : 'easing'
        					}, t.complete); // И сообщим Ractive t.complete что мы закончили с анимацией. Ractive свою очередь засетит все стили так как они были до анимации.
        				});
        			} else {
        				//Либо это удаление элемента из DOM, либо нам не разрешают анимацию (param.go)
        				t.complete();
        			}
        		}
        	}

Вот и все, 230 строк с коментариями (ооочень подробными), темплейтами и js кодом. Фактически никакого поиска по DOM — красота.
Ну и ще раз рабочий код можно посмотреть тут jsfiddle.net/maxtern/e2mk0tn3

Выводы


Счастье заключается в том, что в данном примере фактически ни разу не пришлось искать что-то в DOM, искать ссылки на объекты, проверять состояние компонента.
Все декларативненько и реактивненько, как и было обещано. Никто не кидается друг в друга миллионами событий.
Очень мало «склеивающего» вьюшки с моделями кода. Да и вообще очень мало кода.

Потом Ractive работает очень быстро. Поскольку вам не нужно копаться постоянно в DOM и он точно знает, в каком месте нужно что-то поменять,
перерисовки вьюшек фактически не происходит, поэтому он работает быстро. К примеру, в Backbone мы иногда вызываем render и перерисовываются куча вьюшек, даже те, которые не поменялись.

Также его можно использовать абсолютно с любыми другими библиотечками, например, с тем же Backbone — мы берем из него Model и Route.

Так же на сайте Ractive есть интерактивный туториал, который можно пройти и за 30-50 минут вы знаете Ractive. т.е. 30-50 минут и вы профи.
Процесс изучения невероятно простой и понятный.

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

Эпилог


В данный момент мы работаем над очередным релизом на сайте проекта Пятнашки, и планируем запустить планировщик питания для web.
Сам планировщик выполнен в виде игры пятнашки (что очевидно из названия проекта) и календаря. На мобиле выглядит так:
image
При всем этом в web еще рисуются графики, и пользователи подгружают и редактируют картинки. В общем, полный интерактив.

Если эта статья будет интересна, я опубликую после релиза как легко в Ractive можно разбивать сложный интерфейс на компоненты, и как они взаимодействуют друг с другом на примере этого самого планировщика.

Так же есть идея написать «Ractive vs. React. Такие реактивные фреймворки».

Официальный сайт: www.ractivejs.org
Особенно хорош и них и туториал: learn.ractivejs.org/hello-world/1
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 53

    +1
    Если делать в twig шаблонах такой код, будут неприятности.
    Есть какой либо решение такой задачи?
      0
      я не профи twing. Но у себя я использую такую конструкцию мета код:

      <script id="template name">
      include ./template_name.ractive
      </script>
      

      все темплейты рактива лежат в отдельных файлах что хорошо. twig тоже судя по всему так умеет.
        0
        да ну и обязательно ставить type='ractive/text' (или любое другое но не javascript или пустое) а то браузер будет пытаться запустить ваш темплейт
        +1
        Конечно решение есть! Внимательнее читаем документацию Twig
        twig.sensiolabs.org/doc/tags/verbatim.html
          0
          Спасибо!
          Как то не попадалось на глаза.
          0
          В TWIG'e можно переопределить синтаксис шаблонизатора, если это конечно сильно необходимо и отказаться от стандартных {{ variable }} / {% condition %}
            0
            Да не нужно там ничего переопределять, смотрите мой коммент выше.
              0
              Вы правы, пока читал статью да писал комментарий — ваш не заметил. Сколько twig'ом пользуюсь не замечал данной функции raw-обработки, возможно из-за отсутствия необходимости для меня в таковой.
                0
                Сам о ней вспомнил, когда появилась необходимость в Ангуляр + Твиг.
          0
          Я так и не понял, можно ли использовать делегирование событий?
          В документации есть совершенно непонятная фраза:
          Because of the way proxy events work, they more or less eliminate the need for event delegation.

          А так, в целом, похоже на ангуляр двухсторонним связыванием.
            +1
            Там идея в том что ractive сам занимаеться делегированием. Поэтому фактически не нужно самим это делать. Там есть крайние случае, но это не очень инетерсная тема.

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

            Почитать можно здесь blog.ractivejs.org/posts/whats-the-difference-between-angular-and-ractive/
            0
            Я так понял зверь этот без роутера?
              +2
              Да Ractive кроме UI вообще ничем не занимается. Выбирайте любой который вам нравится\знаком. Ractive очень легко связывать с другими тулами… начиная от jquery плагинов и заканчивая чем угодно. Мы например берем модель и роутер из Backbone.
              –2
              Да, туториал радует
                +2
                У всех бывают косяки… у меня на новой opera работает отлично. Видимо сайт сверстан только новые браузеры.
                0
                Matreshka.js, статьи о ней на хабре
                  0
                  Я правильно понял идею: у вас есть данные вы пересываете сетеры\геттеры на модели и в них пишите как меняеться UI? добовляя, убирая и измения елементы? Судя по имплементации ToDo это больше похоже на Backbone чем на Ractive
                    –1
                    ложитесь спать
                  +1
                  Есть еще такой интересный проект ripplejs.github.io и vuejs.org
                    +1
                    Cпасибо за ссылки. Очень похожие проекты. Нужно поиграться обязательно. Хочу еще написать обзор Ractive vs. React… может чуть раширить список реактивненького.
                      0
                      Если будете писать обзор — рекомендую составить таблицу сравнения главных фич вышеупомянутых библиотек.
                      Думаю, так будет удобнее, а то глаза разбегаются от подобного разнообразия:)
                    +1
                    В целом понятно, выглядит нормально, но не увидел какой-то новизны или киллер-фичи… пойду пока почитаю "What's the difference between Angular and Ractive?", от вас жду статью Ractive vs. React!
                    Как говорится, всё познаётся в сравнении.
                      0
                      Так там же есть и сравнение Reacive vs React blog.ractivejs.org/posts/whats-the-difference-between-react-and-ractive
                        0
                        Сравнение есть но его на самом деле можно и нужно расширить (в том числе примерами). Особенно про React.
                          0
                          И всетаки стоит глянуть еще сюда vuejs.org/perf. Я пишу на Angular довольно давно, но вот vuejs показался мне очень интересным, тем более подход почти как у Angular.
                            0
                            Интересные ребяра, правда я пока не могу понять в чем их фишка точнее отличее от Ractive. Они говорят что они вдохновлялись Angular и Ractive. Но из обзора ме могу понять что они делают по другому. Строят паралельный DOM? Перерисовывают весь UI?

                            Я так понял, тоже самое что Ractive (паралельный DOM + api инстанса) но все цепляется через дерективы как у Angular но все равно с пимисью mustache?

                            Кстати не подходит если нужно поддерижвать IE8
                      +7
                      Последнее время появилось много библиотек с реализацией двухстороннего связывания, но по мне, эта не тянет на ту, которая приведет нас к «бриллиантовому веку веб-разработки».

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

                      С одной стороны аргументом в пользу таких решений как Ractive.js может быть то, что не стоит стрелять из пушки по воробьям, и если мне лишь нужно вывести комментарии, то незачем подключать для этого такой инструмент как Angular. Но во-первых никогда не знаешь что может понадобиться завтра, а во-вторых при всей его мощи минифицированная версия Angular «весит» в полтора! раза меньше чем минифицированный Ractive.js

                      Поправьте меня, если я не прав.
                        –3
                        107KB vs 137BK действительно Angular легче в размере.

                        Но они используют 2 разных подхода к решению одной и той же задачи. Это не просто 2х сторонний биндинг.
                        Опять же хочу как нить написать отедльный пост и попытаться сравнить.

                        Если кратко: если вы пишете большое приложение… на самом деле большое, или пишите UI под мобилы — вы сталкнетесь с проблемами производительности в Angular. Он проверяет на любое изменение все дерево зависимостей что приводит к плохим результатам в больших приложухах, и самое главное не понятно что с этим всем делать. C паралельным DOM таких проблем нет (точнее они меньше) потому что меняеться только то что должно поменяться + вполне понятно как оптимизировать, отследив что ререндариться.

                        Если у вас маленькое приложение то учить и использовать Ангулар — как вы сказали из пушки по воробьям. А изучение Ангулара та еще радость.

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

                          Angular не перестраивает весь список. Он точно так же делает точечные изменения в DOM, т.е. если изменился только один объект из списка, то обновится только соответствующий ему элемент в DOM не затрагивая остальные.

                          Для примера:
                          jsbin.com/nivitituqane/2/edit
                          Попробуйте удалить один из элементов в инспекторе и нажать на кнопку «Change me» на каком либо элементе. Если бы перестраивался весь список, то удаленный элемент снова появился бы, но этого не происходит.

                          Конечно у Angular есть проблемы с производительностью при большом количестве данных в том же ng-repeat, но честно говоря в своих приложениях не сталкивался с ними. Да и еще стоит проверить не лишен ли таких проблем Ractive.js
                            –2
                            Интересный експеремент. Реально спасибо.

                            Но суть в том что если вы удалили руками ноду то она собственно умрет. Ангулал не перисовывает список а пытаеться искать в доме атирбуты (все) и поменять его. Не находя атрибут он его не меняет. Вобщем ничего страшного.

                            Что бы понять что он перерисовывет весь дом попробуйте в вашем методе добавить елемент в array.

                            $scope.changeItem = function(item){
                                  item.title = "Changed "+item.title;
                                  
                                  $scope.items.push({title:'fun2',content:'Fun2'});
                                  
                                }
                            


                            О магия если вы удалите елемент из дом он вернеться. Чего не произойдет в Рактив.

                            Авторы англар утверждают что «вообще это не важно» — stackoverflow.com/questions/9682092/databinding-in-angularjs/9693933#9693933

                            Я думаю что если вы работаете над небольшим проетктом вам не нужен ангулар. Если вы работаете над действительно большим вы не сможете его применять.

                            Хотя на самом деле я думаю что если вы уже выучили Ангулар — и не работаете в компании которая делает денамический интерфейс для миллионов пользователей — забейте на все и пользуйтесь Ангуляр — реально хорошая штука если ее постигнуть.И авторы правы (ссылка вверху) врядли вы сталкнетесь с проблемой производительности. Если вы еще не потратили моного времени на изучение Ангулара — попробуйте полностью понять за 30 минут Ractive и улыбка непременно окажеться у вас на лице. ИМХО.
                              0
                              В этом случае нужно использовать track by, чтобы Angular мог ассоциировать элемент коллекции с DOM элементом.
                              jsbin.com/qavekiwayebu/1/edit
                              Если не удалять целиком элемент из DOM, а удалить (или изменить) например заголовок внутри элемента, то вы увидите что даже при добавлении нового элемента в коллекцию заголовок не появится снова. При удалении всего элемента он появится заново потому что теперь DOM не соответствует коллекции (на мой взгляд все логично), но опять же не будет перестраивания всего списка, а только добавление удаленного элемента в нужное место.

                              Подробнее здесь:
                              www.bennadel.com/blog/2556-using-track-by-with-ngrepeat-in-angularjs-1-2.htm
                                0
                                Интресный разговор.

                                Покопавшись еще чуть чуть я понял что не правильно понимал что такое «dirty-checking».
                                Я думал что он имеет ввиду что «кода что то изменилось мы пересоздаем весь дом». На самом деле это значит (я так думаю) «Мы отслеживаем все изменения я в данном scope и по всем дерективам проверяем поменялось ли значение по отношению к преведущему или нет и если поменялось то нужно обновлять данный кусок DOM».
                                и тут java.dzone.com/articles/improving-angular-dirty

                                docs.angularjs.org/guide/scope ( Sope Life Cycle);
                                After evaluating the expression, the $apply method performs a $digest. In the $digest phase the scope examines all of the $watch expressions and compares them with the previous value. This dirty checking is done asynchronously. This means that assignment such as $scope.username=«angular» will not immediately cause a $watch to be notified, instead the $watch notification is delayed until the $digest phase. This delay is desirable, since it coalesces multiple model updates into one $watch notification as well as it guarantees that during the $watch notification no other $watches are running. If a $watch changes the value of the model, it will force additional $digest cycle.

                                Ractive — делает дерево зависимостей и обновляет только тот DOM который зависит, т.е. он трекает кто от чего зависит и меняет это только в том случае если что то поменялось в его дереве. О изменениях в дереве он узнает через ractive.set(). ( кстати именно поэтому ангулар может работать без set);

                                Я был не прав в том что думал что ангулар всегда пересоздает весь DOM. Но он полностью просчитывает (dirty checking) состояние отображение на каждое изменение что дорого. Как директх.
                                  +1
                                  Но для меня опять повторюсь ключевым отличием или преимуществом являеться простота Ractive.

                                  т.е. нет никаких внедрение зависимости, котроллеров, $scopes, атрибутов который работают не прозрачно и т.д.
                                  Документацию сложно (может быть это только с моим маленьким мозгом) читать и понимать так как воводиться огромное количество новых концепций которые не интуитивные.

                                  Для того что бы начать писать сложные UI на Ractive нужно 40-50 мин времени и понимать как UI бить на компоненты. Причем не компоненты как это видит Ractive… а на обычные web components.
                                    0
                                    Но для меня опять повторюсь ключевым отличием или преимуществом являеться простота Ractive.
                                    т.е. нет никаких внедрение зависимости, котроллеров, $scopes, атрибутов который работают не прозрачно и т.д.
                                    Можете ещё попробовать Angular Light, он проще чем Angular.js, имхо, и там тоже нет фабрик, модулей, DI, и т.п.
                                    $scopes
                                    Все же Ractive имеет аналоги базовых вещей — для $scope, это частично раздел data.
                          +1
                          Напишите чем это отличается от, например, knockout и backbone, и какие плюсы и минусы.
                            0
                            Про knockout не напишу — на нем ничего не писал.

                            Backbone — если коротко то это модель\коллекции моделей где стандартизированно апи по взимодействию с сервером. на кажое изменение модели они кидают ивенты. Так же есть вьюшки которые просто группируют куски DOM и метод render который вставляет его в DOM.
                            Дальше вы связываете render метод (биндите) разных вьешек с разными моделями. И наступет радость — вы обновили модель — поменялась вьюшка.

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

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

                            Ractive же говорит опишите темплейт и я там буду менять только то что меняеться в модели. Очень точечно и за один цикл. Никаких ивентов… биндингов и прочего.

                            Из возможных минусов — Ractive занимаеться только UI. Больше он ничего не умеет… ни моделью управлять… ни роутера нет… ни валидаторов… вам прийдеться искать что то для этих задачь. Для некотрых это + для некоторых это -.

                              +3
                              От knockout отличается, похоже, тем, что как-то здесь всё запутанней))
                                +1
                                ну backbone это совсем мимо корзины, т.к. там совсем ни слова про реактивность, а вот от нокаута я вижу совсем минимальные различия.
                                +5
                                Смотрю я на этот неровный забор из колбэков семи уровней вложенности, и мне как-то плохеет. Не столько от того, что такой код существует, сколько от того, что его существование никого не удивляет и не возмущает.

                                «Бриллиантовый век», ага.
                                  –3
                                  Вложенность максимум что я нашол — 3. init() -> on('save') -> then(); если я чет не вижу поправте. Учитывая что практически весь код сконцентрирован в init (что может быть не есть феншуй но и не преступленее точно, скорее инкапсуляция для компонента) — то можно смело сказать что вложенность максимум 2. on('save',callback) -> then(callback).

                                  ИМХО не так плохо, коллбек на ивент, коллбек на сетевой запрос. Поправте меня пожалуйста если я что то пропускаю.
                                    0
                                    Для прототипов и одноразовых проектов:
                                    — наклепать нужный функционал сможет вчерашний студент за дешево
                                    — поддерживать код не нужно, при необходимости дешевле переписать с нуля

                                    А как только прототип есть и есть положительные отзывы, то можно браться за разработку «с умом», вводить всякие абстракции и т.п.
                                    Но посудите сами, многие веб проекты написанные на коленке вполне себе работают и приносят деньги, так зачем идти против системы и пытаться продать то, что никому не нужно?
                                    +4
                                    Для сравнения сделал аналог вашего примера на Angular Light, HTML'а вышло меньше, js кода вроде как тоже, и вес библиотек 13кб.
                                      0
                                      Спасибо за пример.

                                      Примеры на самом деле не одинаковые.
                                      Форма была отдельно от коммента. По множественному нажатию на «Добавить комментарий» не додовлялось безконечные формы ответа. Что бы форма ответа была снизу и сверху по условию. Анимация на комент была другая и именно она должна бять. Дата форматировалась в «минут назад» и еще много мелочей. У вас получилось меньше кода и локаничней потому как вы упростили саму задачу.

                                      Если вы не против я утяну к себе ваш пример и доделаю что бы они были действительно одинаковыми что бы использовать в сравнении vs.?
                                        0
                                        Примеры на самом деле не одинаковые. Анимация на комент была другая
                                        Да, но мой пример покрывает требование «озвученное» в самом посте, а т.к. у меня нет информации о детальном ТЗ, поэтому я сделал самое основное — древовидные комментарии с анимацией.

                                        Если вы не против я утяну к себе ваш пример и доделаю что бы они были действительно одинаковыми что бы использовать в сравнении vs.?
                                        Конечно не против, буду благодарен если вы бросите потом мне ссылку на результат.
                                        А вообще, сравнения лучше проводить на каких-то нейтральных примерах вроде todomvc, т.к. некоторые примеры могут быть «заточены» под выдвигаемый фреймворк.
                                          0
                                          Ну как я и писал это пример из реального проекта «Пятнашки» (ЗОЖ сайт) над которым сейчас работаю — поэтому сделанно так как видит дизайнер а не потому что удобно сделать с библиотечкой. Я специально брал реальные требования и дизайн потому как всегда черт в мелочах — типа можно сделать очень легко, но не так как хочет дизайнер и тогда это никому не нужно.

                                          Ссылку кину как сделаю. Спасибо.
                                      –1
                                      До бриллианта в моем понимании не дотягивает. Надо что бы вообще никакого js кода писать не приходилось, а был набор готовых виджетов и REST сервер для вливания в них данных.
                                        +1
                                        А мне понравился Ractive.
                                        Для небольших интерактивных штук он подходит хорошо.
                                        Если не рассматривать большие веб-сервисы, а говорить про традиционные сайты малого и среднего формата, да еще и построенные на какой-либо CMS, то Angular туда иной раз ну просто не ввернуть.
                                        В то же время Ractive позволяет неплохо решать точечные возникающие задачи.
                                          +5
                                          Соглашусь с oledje. Ractive это подобие Angular, не совсем успешная попытка сделать проще. Местами еще сырая.

                                          Я из лагеря которые попробовали и то и другое. Рактив первым — когда столкнулся с задачей сделать сложное миниприложение. А через полгода пришлось разбираться с проектом на ангуларе. Да, с рактивом (после «pure jQuery» подхода) сначала было счастье, но затем начал ловить кучу мелких, но пакостных и сложновоспроизводимых багов, сообщал автору с примерами на jsfiddle. Некоторые он чинил, некоторые обещал посмотреть но забил. Самому же не было времени разбираться, нужен был рабочий инструмент. С энгуларом проблем не было, все работает четко и документации куча. НЕ всё ясно сразу, но и НЕ всё нужно сразу.

                                          Все различия приводимые автором рактива — не плюсы, а ограничения рактива. Энгулар может работать в режиме рактива, но обратное не верно. В энгуларе таки совсем не обязательно использовать свой роутинг, DI, тесты, директивы, сервисы, фабрики и прочее, используй свое, или юзай только датабиндинг — добро пожаловать. Но когда проект (да и разработчик) начнет расти — этот набор пригодится, хотя бы в качестве best practices. Также не стоит заявлять что контроллеры это что-то что необходимо только в энгулар — в рактиве у вас контроллер внутри var Comments = Ractive.extend({...}). Еще автор рактива пугает некими $scope.$digest и $scope.$apply — но в «простом» режиме (т.е. в режиме имитации рактива) с ними скорее всего даже не придется столкнуться/понимать.

                                          Я тоже сначала купился на простоту рактива. Но в работе обнаружил множество сложновоспроизводимых пакостных ошибок. Плюс библиотека сыровата, много чего не хватает. Например: данные обязательно нужно обновлять через Ractive.set(), иногда это не очень удобно (когда скажем кусок данных передаем в метод, и тот не знает всего пути). Банально не хватало Ractive.remove(whatever), потому что Ractive.set(whatever, null) не удаляет whatever, да и не должен. Еще пришлось сделать костыль вроде getRactivePathByDiv() — это когда надо что-то менять у соответственного объекта при клике на связанный div (в энгуларе это решается из коробки и чуть иначе: с помощью аттрибута ng-click='doWhatever(object)' — несмотря на олдскульность вполне удобно). Впоследствии еще обнаружил что в ractive очень не хватает аналога ngIf (ng-if) — возможности вырезать из дома HTML который не отображается (но влияет на скорость парсинга). А размер дома сильно порой влияет на плавность анимации.

                                          Короче говоря, я бы посоветовал начать сразу с энгулара. Это не так сложно как кажется, точнее работать в режиме рактива точно несложно. Но намного полезнее с точки зрения обучения правильным подходам, включая: переходить на deferred вместо коллбэков, к DI и изоляции/тестируемости, устранить надобность в jQuery и её манипуляциях (по крайней мере в контролерах). И много чего еще, не говоря уже о возможности расширяьт HTML новыми тэгами, что самом деле круче декларативности — ведь это декларативность с инкапсуляцией. Впрочем, консерваторы могут опять же обойтись и без этого — angularjs довольно либеральный во всех отношениях.
                                            0
                                            Спасибо за опыт.

                                            По поводу нехватки в Ractive фич. Данные нужно обновлять через set — если не нужно поддерживать IE8 и меньше — то можно поставить magic mode и он будет использовать геттеры сеттеры что бы слушать уведомаления о изменении. и можно будет писать var comment.text = 'нет'.
                                            Про «remove» согласен нехватает. Делать racitve.set('prop',null) странно.

                                            getRactivePathByDiv — костыль не нужен, посмотрите как передается текущий объект e.context в моем примере. так же можно делать так
                                            on-click='doWhatever,{{object}}' и вторым параметрав в doWhatever будет object.
                                            По поводу if — и блоков которые не видны, тоже все есть. посмотрите мой пример там есть {{#if}} и {{#replys.comments > 0}}

                                            Возможно вы пользовались старой версией либы. Сейчас все проблемы описанные решены.

                                            И так же согалсен что знаметиные родители с большой командой поддержки — это тоже +.
                                            +1
                                            Комментарии интересные, спасибо всем за то что делитесь опытом, особенно тем кто уже пробовал ractive и другие. Автор, жду от вас статьи, как и обещали.
                                              0
                                              Вы упомянули в статье, что Ractive работает очень быстро, не могли бы вы добавить Ractive к «местному» бенчмарку.
                                              Если уже есть какие-то готовые бенчмарки, то интересно было бы посмотреть.
                                                0
                                                Добавил Ractive (оптимизированный вариант) в бенчмарк, для данного теста он оказался медленнее чем Angular.js, а если использовать штатные push/set то он становиться ещё медленнее раз в 10.
                                                +1
                                                А есть примеры/ссылки на проекты, которые используют Ractive? Историю/стабильность самого Ractive и т.п.
                                                Понятно, что за Angular и React стоят крупные компании, хотелось бы каких-то «гарантий» или уверенности в плане Ractive.

                                                Only users with full accounts can post comments. Log in, please.