20.02.2015

Fest, BEM и data-binding.

Fest и BEM

Fest и BEM: Создание блока button.xml

			<?xml version="1.0"?>
			<fest:template>
				#!+ <fest:set name="button">
					#!+ <div class="button">  боль
						<fest:value>params.text</fest:value>
					#!- </div>
				#!- </fest:set>
			</fest:template>
		

Fest и BEM: Создаем мастер шаблон со всеми блоками

			<?xml version="1.0"?>
			<fest:template context_name="ctx">
				#! <fest:include src="buttom.xml"/>
				#!+ <!-- Динамически подключаем блок по его имени -->
				#!- <fest:get select="ctx.block">ctx.params</fest:get>
			</fest:template>
		

Fest и BEM: Использование

			<script src="fest/lego.js"></script>
			<script>
				#! var template = window.fest["lego"];
				#!+ var html = template({
					#! block: "button",
					#!+ params: {
						text: "Click me!"
					#!- }
				#!- });
			</script>
		

Fest и BEM

Тут не удобно всё:

Что же делать?

bem:block="…"

bem:elem="…"

<bem:mod />

Кнопка

			<?xml version="1.0"?>
			<fest:template>
				#!+ <div bem:block="button">
					<fest:value>params.text</fest:value>
				#!- </div>
			</fest:template>
		

Мастер шаблон

			<?xml version="1.0"?>
			<fest:template context_name="ctx">
				#! <fest:include src="buttom.xml"/>
				#! <fest:include src="icon.xml"/>
				#! <!-- только include, ничего лишнего -->
			</fest:template>
		

Используем

			<script src="fest/lego.js"></script>
			<script>
				#! var template = window.fest["lego"];
				#!+ var html = template["button"]{
						#! text: "Click me!"
				#!- });
				#! // Ничего лишнего!
			</script>
		

fest:block

Да, это сахар над блоками, но не только!
Усложним пример и добавим:

Кнопка + Иконка

			<?xml version="1.0"?>
			<fest:template>
				<div bem:block="button">
					#! <bem:mod name="icon" test="ctx.icon"/>
					#!+ <fest:if test="ctx.icon">
						#! <bem:icon>{ mods: ctx.icon }</bem:icon>
					#!- </fest:if>
					<fest:value>params.text</fest:value>
				</div>
			</fest:template>
		

Кнопка + Иконка

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

Ну, и!

BINDING!

В чем же суть?

Посмотрите на этот пример:

			<fest:set name="hello">
				<div class="hello">
					#!+ <h2>
						#! Hello<:fest:space/>
						#! <fest:value>ctx.name || "%username%"</fest:value>!
					#!- </h2>
				</div>
			</fest:set>
		

Fest сгенерирует это:

			__fest_blocks.hello = function (ctx) {
				#! var __fest_buf = "";
				#! __fest_buf += ("<div><h2>Hello ");
				#!+ try {
					#! __fest_buf += (__fest_escapeHTML(ctx.name || "%username%"))
				} catch (e) {
					#! __fest_log_error(e.message + "5");
				#!- }
				#! __fest_buf += ("!</h2></div>");
				#! return __fest_buf;
			};
		

Реальная работа: полный цикл

			var data = { name: "" };
			#! var lego = fest["lego"];
			#! var el = document.getElementById("playground");
			#! el.innerHTML = lego({ block: "hello", params: data });
			#! data.name = "Fest";
			#! el.innerHTML = lego({ block: "hello", params: data });
		

BINDING

Для этого нам необходимо

Используем bem:block:

			<div bem:block="hello">
				#!+ <h2>
					Hello<:fest:space/>
					<fest:value>ctx.name || "%username%"</fest:value>!
				#!- </h2>
			</div>
		

А теперь самое интересно!

			#! var lego = fest.withBindings("lego");
			#! var el = document.getElementById("playground");
			#! var hello = new fest.ModelView({ text: "" });
			#! el.innerHTML = lego["hello"](hello);
			#! hello.$set("name", "Fest");
			#! hello.$set("name", "Binding");
			#! // Красота!
		

Но как?

Но как?

			__fest_blocks.hello = function (ctx) {
				#! var __fest_buf = "", xid = "-" + __gid++;
				#! __fest_buf += ("<div id='"+xid+"'><h2>Hello ");
				#!+ /*$V*/ try {
					__fest_buf += (__fest_escapeHTML(ctx.name || "%username%"))
				} catch (e) {
					__fest_log_error(e.message + "5");
				} /*V$*/
				__fest_buf += ("!</h2></div>");
				#!- return __fest_buf;
			};
		

fest.withBindings

fest.withBindings("lego")

			__fest_blocks.hello = function (ctx) {
				var __fest_buf = "", xid = "-" + __gid++, __xb = [], __bid;
				__fest_buf += ("<div id='"+xid+"'><h2>Hello ");
				#! __bid = __xb.push({}) - 1; // добавлям связку
				#! __xb[__bid].name = "V"; // название
				#!+ __xb[__bid].$render = function () {
					#! var __fest_buf = "";
					#! try { .. } catch (e) { .. }
					#! return __fest_buf;
				#!- };
				#!+ __fest_buf += "<!--" + __bid + "-->";
					// Тут тот же самый код с try-catch, как и в функции $render
				#!- __fest_buf += "<!--/" + __bid + "-->";
				#! ctx && ctx.$bind && ctx.$bind(xid, __xb);
				return __fest_buf;
			};
		

API: fest.withBindings

			fest.withBindings.add({
				#! id: "V", // fest:value
				#! dom: "wrap", // обернуть код коментарием для связи
				#! render: true, // создать функцию $render
				#!+ expr: function (value) {
					// Для $render нам больше не нужно экранирование
					return value.replace(/\b__fest_escape[^(]+/g, '');
				#!- }
			});
		

API: fest.withBindings

			fest.withBindings.add({
				id: "IF", // fest:if
				dom: "wrap",
				render: true, // всё что внутри if'а
				props: { last: '!!__fest_if' },
				test: function () { // функция проверки изменений
					var __fest_if;
					this.getter(); // вместо этого будет вставлен try..catch
					if (this.last !== !!__fest_if) {
						this.last = !!__fest_if;
						return true;
					}
				}
			});
		

fest.ModelView

new fest.ModelView(attrs)

modelView.$bind(...)

			// Идем по всему DOM и связываем комментарии
			if (node.nodeType === 8) {
				var idx = node.nodeValue;
				if (idx.charAt(0) !== "/") {
					this.$binds[idx].startEl = node; // начало
				} else {
					this.$binds[idx.substr(1)].endEl = node; // конец
				}
			}
		

А дальше...

fest.ModelView.register

			// Описываем компонент
			fest.ModelView.register("b-dropdown", {
				mount: function () { /* в DOM'е */ },
				unmount: function () { /* удалил из DOM'а */ },
				chooseItem: function (item) {
					/* обработка клина на item */
				}
			});
		

fest.ModelView.register("b-dropdown",{..})

			<div bem:block="b-dropdown">
				<div bem:elem="ctrl"><fest:value>ctx.ctrl</fest:value></div>
				<fest:for-each data="ctx.items" as="item">
					<div bem:elem="item" on-click="ctx.chooseItem(item);">
						item.text
					</div>
				</fest:for-each>
			</div>
		

The End