Front-end шаблонизатор

Минулої свою статті я посвітив опису «велосипеда» (завантажувача і шаблонизатора в рамках «легкого» framework'а). Волею доль, для пари проектів я був змушений виділити шаблонизатор і зробити його standalone версію, збагативши при цьому рядом нових можливостей. Саме про front-end шаблонизаторе і піде мова.

Але щоб заощадити ваш час, перш я позначу тих, кому ця стаття може бути цікавою (бо букв буде багато):

  • Ви front-end розробник, і вам цікаво використання шаблонів.
  • Ви back-end розробник, і вам цікаво використання шаблонів front-end'е.
  • Ви давно шукаєте який-небудь інструмент для систематизації своєї колекції UI-control'ів, що накопичилася за кілька років.
  • Ви цікавитеся розробкою web-компонентів.
  • Вам хочеться висловити критичні зауваження та порекомендувати angularJS.
  • У вас є вільний час і вам цікаво почитати про чергове велосипеді.
  • У вас немає вільного часу, але вам цікаво.
  • Ви хороший і допитлива людина.


Проект називається Flex.Patterns, але для простоти я буду його називати просто patterns. Нижче буде кілька прикладів, які ви легко зможете відтворити і самі. На відміну від Flex, описаного в минулій статті, patterns не вимагає ніяких налаштувань і танців з бубном – підчепив і користуйся. Patterns взагалі досить простий, що було моєю головною метою.

Наприклад, шаблон в patterns – це просто HTML-сторінка і нічого більше. Ніякого специфічного синтаксису зразок того, що використовується в EJS і багатьох інших шаблонизаторах.

<ul>
<% for(var i=0; i<supplies.length; i++) {%>
<li><%= supplies[i] %></li>
<% } %>
</ul>


Весь синтаксис patterns обмежується трьома визначеннями:

  • Hook. {{name_of_hook}}. Зачіпка, за допомогою якої ви можете позначати місця в шаблоні для вставки вмісту.
  • Model. {{::name_of_ reference }}. Так ви можете вказати на властивість вузла або його атрибут, який повинен бути пов'язаний з об'єктом моделі для подальшого маніпулювання.
  • DOM. {{$name_of_reference}}. Вказавши за допомогою цієї мітки на сайті, ви отримаєте можливість дуже швидко звертатися до цього сайту, змінювати його, прикріплювати події і робити інші рутинні речі.


Створення шаблону



Ну давайте на прикладі. Створимо popup для авторизації користувача. Нам знадобляться чотири шаблону:

  • Спливаюче вікно — html.
  • Розмітка для вікна авторизації — html.
  • Поле для тексту — html.
  • Кнопка — html.


Нижче шаблон для спливаючого вікна (popup).

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Flex.Template</title>
< meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel="stylesheet" type="text/css" href="pattern.css" />
</head>
<body>
<div data-style="Popup" id="{{id}}">
<div data-style="Popup.Container">
<div data-style="Popup.Title">
<p data-style="Popup.Title">{{title}}</p>
</div>
<div data-style="Popup.Content">{{content}}</div>
<div data-style="Popup.Bottom">
<p data-style="Popup.Bottom">{{bottom}}</p>
</div>
</div>
</div>
</body>
</html>


Як ви вже помітили – це самий звичайний HTML-файл. В HEAD ви можете підключати CSS і JS файли, які будуть автоматично підключені разом з шаблоном і закешовані.

Кешування – це важлива частина patterns. І самі шаблони (HTML) і ресурси (CSS і JS) зберігаються в localStorage, а це означає, що при повторному використанні шаблону, всі дані будуть взяті не з сервера, а з клієнта, що найсприятливішим чином позначається на швидкості відтворення. Крім того patterns сам стежить за актуальністю кешу: всякий раз patterns запитує HEADERs всіх шаблонів і їх ресурсів; і якщо щось змінилося, patterns самостійно оновити кеш, щоб підтримувати всю систему в актуальному стані. Але, повернемося до нашого вікна авторизації.

Шаблон розмітки (далі буду наводити тільки зміст тега BODY, для економії місця)

<div data type="Pattern.Login">
<p>Login</p>
{{login}}
<p>Password</p>
{{password}}
<div data type="Pattern.Controls">{{controls}}</div>
</div>


Поле для введення тексту (в нашому випадку це буде логін і пароль)

<p>{{::value}}</p>
<div data type="TextInput.Wrapper">
<div data type="TextInput.Container">
<input data type="TextInput" type="{{type}}" value="{{::value}}" name="TestInput"/>
</div>
</div>


Зверніть увагу на те, що ми зв'язали INPUT.value і P. innerHTML через змінну названу value, використовуючи наведену вище мітку {{::value}}. Таким чином, якщо якийсь текст буде введений в INPUT, то він буде відображений і в зв'язаному параграфі. Крім того, створена мінлива value буде поміщена в модель.

Ну і останній шаблон, необхідний для вікна авторизації – кнопка.

<a data type="Buttons.Flat" id="{{id}}">{{title}}</a>


Перш ніж піти далі, варто обмовитися. Той факт, що patterns в якості шаблонів використовує повноцінні HTML файли дає вам можливість відкривати їх окремо від сторінки, де вони використовуються, а це дає можливість швидкого налагодження стилів і логіки, якщо така передбачена.

Приєднання шаблону



Шаблон може бути приєднаний до сторінки (тобто отрисован) двома способами:

  • Через виклик JavaScript методу
  • Через HTML розмітку.


Будь використовувати – залежить виключно від поставленого завдання. Наприклад, якщо шаблон повинен бути отрисован відразу після завантаження сторінок, то краще приєднувати його через розмітку. Якщо ж ми говоримо про щось на кшталт нашого тестового вікна авторизації, то тут більш доречний виклик через JavaScript. Давайте розглянемо обидва методи.

Вивід через JavaScript



За рендеринг шаблону відповідає метод get — _patterns.get(), який повертає екземпляр класу шаблону, який ви можете змонтувати (прикріпити до зазначеного вузла), через метод – render. Погляньте на приклад нижче, і все стане ясно.

var id = flex.unique();
_patterns.get({
url : '/patterns/popup/pattern.html',
node : document.body,
hooks : {
id : id,
title : 'Test dialog window',
content : _patterns.get({
url : '/patterns/patterns/login/pattern.html',
hooks : {
login : _patterns.get({
url : '/patterns/controls/textinput/pattern.html',
hooks : {
type: 'text',
}
}),
password: _patterns.get({
url : '/patterns/controls/textinput/pattern.html',
hooks : {
type: 'password',
}
}),
controls: _patterns.get({
url : '/patterns/buttons/flat/pattern.html',
hooks : [{ title: 'login', id: 'login_button' }, { title: 'cancel', id: 'cancel_button' }]
}),
}
})
}
}).render();


Самий важливий параметр – це url, де ми вказуємо місце, звідки брати шаблон. Не менш важливий параметр – це hooks. Пам'ятаєте в шаблонах ми вказували місця для контенту через мітку – {{name}}. У параметрі hooks, ми визначаємо контент для кожної такої мітки.

Повний опис всіх параметрів, які приймає метод _patterns.get(), ви можете знайти тут. А на результат цього прикладу можна подивитися тут.

Але йдемо далі.

Вивід через HMTL розмітку



У ситуації, коли сформований шаблон нам потрібен негайно після завантаження сторінки, ми можемо, використовуючи тег PATTERN, помістити шаблон безпосередньо в розмітку.

<pattern src="/patterns/popup/pattern.html" style="display:none;">
<id>0</id>
<title>Test dialog window</title>
<content src="/patterns/patterns/login/pattern.html">
<login src="/patterns/controls/textinput/pattern.html">
<type>text</type>
</login>
<password src="/patterns/controls/textinput/pattern.html">
<type>password</type>
</password>
<controls src="/patterns/buttons/flat/pattern.html">
<id>login_button</id><title>login</title>
<id>cancel_button</id><title>cancel</title>
</controls>
</content>
</pattern>


У даному випадку для визначення hook'ов ми використовуємо однойменні теги. Тобто наведені нижче дві конструкції за своїм змістом ідентичні.

<pattern src="/patterns/popup/pattern.html" style="display:none;">
<id>0</id>
<title>Test dialog window</title>
...
</pattern>


_patterns.get({
url : '/patterns/popup/pattern.html',
hooks : {
id : 0,
title : 'Test dialog window',
...
}
}).render();


Зверніть увагу, що тег PATTERN ми використовуємо тільки для кореневого вузла, а далі лише додаємо властивість SCR, щоб позначити, що в якості контенту hook (зачіпки) буде використовуватися вкладений шаблон.

Тобто наступна розмітка означає, що patterns повинен знайти шаблон за адресою, вказаною у SRC і застосувати його з hook'ом type в значенні «text».

<login src="/patterns/controls/textinput/pattern.html">
<type>text</type>
</login>


Тут ви можете подивитися на працюючий приклад. Відкрийте sources of page, щоб переконатися, що ніяких дзвінків JavaScript немає і початкової розмітки сторінки присутній тег PATTERN з необхідними для відтворення даними.

Повторення шаблону



Дуже часто нам буває необхідно повторити шаблон багаторазово. Самим яскравим прикладом цього служить таблиця. Щоб її створити нам знадобиться два шаблону.

Шаблон таблиці.

<table data type="Demo.Table">
<tr>
<th>{{titles.column_0}}</th>
<th>{{titles.column_1}}</th>
<th>{{titles.column_2}}</th>
<th>{{titles.column_3}}</th>
</tr>
{{rows}}
</table>


Шаблон рядка в таблиці.

<tr>
<td>{{column_0}}</td>
<td>{{column_1}}</td>
<td>{{column_2}}</td>
<td>{{column_3}}</td>
</tr>


Маючи ці два шаблони і дані, ми можемо промалювати нашу таблицю.

var data_source = [];
for (var i = 0; i < 100; i += 1) {
data_source.push({
column_0: (Math.random() * 1000).toFixed(4),
column_1: (Math.random() * 1000).toFixed(4),
column_2: (Math.random() * 1000).toFixed(4),
column_3: (Math.random() * 1000).toFixed(4),
});
}
_patterns.get({
url: '/patterns/table/container/pattern.html',
node: document.body,
hooks: {
titles: {
column_0: 'Column #0',
column_1: 'Column #1',
column_2: 'Column #2',
column_3: 'Column #3',
},
rows: _patterns.get({
url: '/patterns/table/row/pattern.html',
hooks: data_source,
})
}
}).render();


Тут ви можете знайти працюючий приклад.

Отже, щоб повторити певний шаблон кілька разів нам достатньо передати значення hook (зачіпки) у вигляді масиву даних. І, як ви могли помітити, для повторення шаблону при його визначенні в HTML ми повторюємо значення hook стільки разів, скільки нам потрібно, як це було раніше продемонстровано з кнопками до вікна авторизації.

<controls src="/patterns/buttons/flat/pattern.html">
<id>login_button</id><title>login</title>
<id>cancel_button</id><title>cancel</title>
</controls>


Так само зверніть увагу на те, що імена hook'ів в заголовках визначені через точку {{titles.column_0}}, що дозволяє нам функції рендеринга використовувати більш осмислене визначення їх значень. Так, всі заголовки визначаються в об'єкті titles.

Контролери та функції зворотного виклику



По суті в patterns контролер і функція зворотного виклику – це одне і теж. Відмінність лише в місці зберігання.

Як ви могли здогадатися, функція зворотного виклику визначається в момент візуалізації шаблону.

_patterns.get({
url : 'some_url',
callbacks: {
//Callback-function definition
success: function (results) {
var instance = this,
dom = results.dom,
model = results.model,
binds = results.binds,
map = results.map,
resources = results.resources;
...
}
},
}).render();


А от щоб створити контролер, потрібно створити JS файл наступного змісту

_controller(function (results) {
var instance = this,
dom = results.dom,
model = results.model,
binds = results.binds,
map = results.map,
resources = results.resources;
...
});


Потім вам досить прикріпити його до вашого шаблону.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Flex.Template</title>
< meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel="stylesheet" type="text/css" href="pattern.css" />

<!-- Attach controller of template -->
<script type="text/javascript" src="conroller.js"></script>

</head>
<body>
<div data-style="Popup">
<div data-style="Popup.Container">
<div data-style="Popup.Title">
<p data-style="Popup.Title">{{title}}</p>
</div>
<div data-style="Popup.Content">{{content}}</div>
<div data-style="Popup.Bottom">
<p data-style="Popup.Bottom">{{bottom}}</p>
</div>
</div>
</div>
</body>
</html>


І все, контролер готовий. Тепер все що визначено всередині вашого контролера буде запускатися кожен раз після того, як шаблон отрисован.

Однак найбільший інтерес представляє об'єкт results, який передається і в контролер, і у функцію зворотного виклику.

Модель та зв'язку



Два важливі об'єкти, які ви отримуєте – це model і binds

var model = results.model;
var binds = results.binds;


Що б продемонструвати, що є що, давайте змінимо шаблон для рядка таблиці наступним чином:

<tr>
<td style="background:{{::background_0}};">{{column_0}}{{::column_0}}</td>
<td style="background:{{::background_1}};">{{column_1}}{{::column_1}}</td>
<td style="background:{{::background_2}};">{{column_2}}{{::column_2}}</td>
<td style="background:{{::background_3}};">{{column_3}}{{::column_3}}</td>
</tr>


Як ви бачите, ми додали пару зв'язків. По-перше, ми зв'язали властивість background кожної комірки зі змінною background_n. Те ж саме ми зробили і зі значеннями самих клітинок, зв'язавши їх із змінною column_n.

Тепер в контролері (або функції зворотного виклику) ми можемо отримати доступ до пов'язаних властивостями вузлів.

_patterns.get({
...
callbacks : {
success: function (results) {
(function (model) {
var fun = function () {
var r = Math.round(19 * Math.random()),
c = Math.round(3 * Math.random());
model.__rows__[r]['column_' + c] = (Math.random() * 1000).toFixed(4);
model.__rows__[r]['background_' + c] = 'rgb(' + Math.round(255 * Math.random()) + ', ' + Math.round(255 * Math.random()) + ', ' + Math.round(255 * Math.random()) + ')';
setTimeout(fun, Math.ceil(50 * Math.random()));
};
fun();
}(results.model));
}
}
}).render();


Подивитися на злетівшу з котушок таблицю можна тут.

Отже, об'єкт model містить посилання на зв'язані значення. Зверніть увагу на властивість __rows__. Через дану конструкцію __hook__, позначаються рівні вкладеності hook'ів. Так як дані містяться не в кореневому шаблоні (шаблон таблиці), а вкладені в hook rows, то і доступ до них можливий через model.__rows__. Подвійне ж підкреслення введено як превентивний захід від конфліктів імен.

Якщо ви пам'ятаєте, то в шаблоні вікна авторизації ми пов'язували INPUT.value з P. innerHTML. У функції зворотного виклику ми отримуємо і посилання на value.

_patterns.get({
url : '/patterns/popup/pattern.html',
node : document.body,
hooks : {
id : id,
title : 'Test dialog window',
content : _patterns.get({
url : '/patterns/patterns/login/pattern.html',
hooks : {
login : _patterns.get({
url : '/patterns/controls/textinput/pattern.html',
hooks : {
type: 'text',
}
}),
password: _patterns.get({
url : '/patterns/controls/textinput/pattern.html',
hooks : {
type: 'password',
}
}),
controls: _patterns.get({
url : '/patterns/buttons/flat/pattern.html',
hooks : [{ title: 'login', id: 'login_button' }, { title: 'cancel', id: 'cancel_button' }]
}),
}
})
},
callbacks: {
success: function (results) {
var instance = this,
model = results.model;
model.__content__.__login__.value = 'this new login';
}
},
}).render();


З model розібралися, але що ж таке binds? А binds за своєю структурою теж саме що і model, за одним лише винятком – «на кінці» не значення, а методи.

success: function (results) {
var instance = this,
dom = results.dom,
binds = results.binds,
id = null;
//Add handle
id = binds.__content__.__login__.value.addHandle(function (name, value) {
var obj = this;
});
//Remove handle
binds.__content__.__login__.value.removeHandle(id);
}


І їх (методів) усього два:

  • addHandle
  • removeHandle


Як ви вже здогадалися, перший прикріплює обробник подій, а другий видаляє його. Таким чином, ви можете повісити свою функцію до якого-небудь властивості моделі, яка спрацьовуватиме всякий раз, коли це властивість змінюється.

DOM і карта



Ще два цікавих об'єкта – це DOM і map.

var dom = results.dom;
var map = results.map;


Змінимо трохи шаблон кнопки для нашого вікна авторизації, щоб продемонструвати можливості об'єкта dom.

<a data type="Buttons.Flat" id="{{id}}" {{$button}}>{{title}}</a>


Отже, ми додали посилання {{$button}} до сайту кнопки. Таким чином, ми відзначили вузол, щодо якого patterns створить колекцію методів для роботи з даним вузлом.

success: function (results) {
var instance = this,
dom = results.dom;
dom.listed.__content__.__controls__[0].button.on('click', function () {
alert('You cannot login. It\'s just test. Login is "' + model.__content__.__login__.value + '", and password is "' + model.__content__.__password__.value + '"');
});
dom.listed.__content__.__controls__[1].button.on('click', function () {
alert('Do not close me, please.');
});
dom.grouped.__content__.__controls__.button.on('click', function () {
alert('This is common handle for both buttons');
});
}


Як ви бачите, ми отримали можливість прикріпити обробники подій до кнопок форми. Повний перелік всіх методів, що йдуть «з коробки» ви знайдете тут. Там же є і опис того, як додати свої власні методи.

Тут же я лише зверну вашу увагу на те, що об'єкт dom має дві властивості:

  • grouped
  • listed


Перше властивість містить згруповані методи. Тобто, так як на формі у нас дві кнопки, то при зверненні, наприклад, до методу on (прикріплення подій), ми прикріпимо подія відразу до двох кнопок. Якщо ж нам потрібен доступ до кожної окремої кнопки, то нам необхідно використовувати властивість listed.

У свою чергу об'єкт map дає нам можливість швидкого пошуку вузлів, так як обмежує пошук контекстом шаблону або його частин.

success: function (results) {
var instance = this,
map = results.map,
nodes = null;
//Will find all P in whole popup
nodes = map.__context.select (p');
//Will find all P inside popup in content area
nodes = map.content.__context.select (p');
//Will find all P in textbox-control of login
nodes = map.content.login.__context.select (p');
}


Тобто map.content.login.__context.select (p') буде шукати всі параграфи тільки усередині шаблону, що відноситься до шаблону текстового поля, визначеного для вказівки логіна.

Ви можете використовувати об'єкт map для швидкого пошуку вузлів та отримання посилань на них.

Обмін даними



Ну і нарешті останній об'єкт, який передається у функцію зворотного виклику – це resources. Все просто – це механізм обміну даними. Так, при візуалізації шаблону ви можете визначити властивість resources.

_patterns.get({
url : '/patterns/popup/pattern.html',
node : document.body,
hooks : {
id : id,
title : 'Test dialog window',
content : _patterns.get({
url : '/patterns/login/pattern.html',
hooks : {
login : _patterns.get({
url : '/patterns/controls/textinput/pattern.html',
hooks : {
type: 'text',
}
}),
password: _patterns.get({
url : '/patterns/controls/textinput/pattern.html',
hooks : {
type: 'password',
}
}),
controls: _patterns.get({
url : '/patterns/buttons/flat/pattern.html',
hooks : [{ title: 'login', id: 'login_button' }, { title: 'cancel', id: 'cancel_button' }]
}),
},
})
},
resources: {
field1 : 'one',
field2 : 'two'
},
callbacks: {
success: function (results) {
var instance = this,
resources = results.resources;
window.console.log(resources.field1);
window.console.log(resources.field2);
//Result in console:
//one
//two
}
},
}).render();


Саме воно і буде передано у функцію зворотного виклику, як це продемонстровано на прикладі. Таким чином, ви отримуєте можливість обміну даними між моментами: до малювання і після.

Умови або мінливі шаблони



Насправді це найбільш цікава частина patterns (з моєї точки зору, звичайно), тому що пропоновані тут підходи можуть вас трохи спантеличити. Але, про все по порядку.

Отже, хороший шаблон не може бути абсолютно статичним і повинен якось змінюватися залежно від даних, з якими він буде отрисован. Переважна кількість шаблонизаторов для цієї мети використовує змішання синтаксисів, вставляючи безпосередньо в розмітку логіку. Так це робить, наприклад, згаданий на початку статті EJS.

<ul>
<% for(var i=0; i<supplies.length; i++) {%>
<li><%= supplies[i] %></li>
<% } %>
</ul>


Щоб продемонструвати те, як подібні завдання вирішує patterns давайте повернемося до нашого прикладу вікна авторизації і доопрацюємо шаблон текстового поля таким чином, щоб на випадок його використання для пароля, користувачеві переглядати підказка з припустимими символами.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Flex.Template</title>
< meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel="stylesheet" type="text/css" href="pattern.css" />
<!--Attach JS file with condition-handle-->
<script type="text/javascript" src="conditions.js"></script>
</head>
<body>
<p>{{::value}}</p>
<div data type="TextInput.Wrapper">
<div data type="TextInput.Container">
<input data type="TextInput" type="{{type}}" value="{{::value}}" name="TestInput" {{$input}}/>
</div>
<!--type=password-->
<div data type="TextInput.Info.Icon"></div>
<div data type="TextInput.Info.Popup">
<p>You can use in password only letters, number and _</p>
</div>
<!--type-->
</div>
</body>
</html>


Отже, як ви бачите, ми трохи додали нової розмітки, а саме:

<!--[condition_name]=[condition_value]-->
<div data type="TextInput.Info.Icon"></div>
<div data type="TextInput.Info.Popup">
<p>You can use in password only letters, number and _</p>
</div>
<!--[condition_name]-->


Таким чином ми можемо визначати умови, обрамляючи потрібну частину розмітки HTML-коментарі.

Так само ви не могли не помітити і прикріпленого JS файлу – conditions.js. Ось його зміст:

_conditions({
type: function (data) {
return data.type;
}
});


Як ви можете бачити, там визначена функція (type) відповідає назві умови в розмітці.

Так що ж станеться після відтворення оновленого шаблону вікна авторизації? Логіка дій patterns буде досить простий: виявивши умови шаблон текстового поля, patterns спробує знайти функцію type (за умови імені). Знайшовши цю функцію, patterns передасть їй значення hook'ів (аргумент функції – data). Якщо ця функція поверне визначена умови значення password, то додаткова частина розмітки буде включена в шаблон.

Тут працюючий приклад нашого оновленого вікна авторизації.

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

_patterns.get({
url : '/patterns/popup/pattern.html',
node : document.body,
hooks : {
id : id,
title : 'Test dialog window',
content : _patterns.get({
url : '/patterns/login/pattern.html',
hooks : {
login : _patterns.get({
url : '/patterns/controls/textinput/pattern.html',
hooks : {
type: 'text',
},
conditions : {
type: function (data) {
return data.type;
}
},
}),
password: _patterns.get({
url : '/patterns/controls/textinput/pattern.html',
hooks : {
type: 'password',
},
conditions : {
type: function (data) {
return data.type;
}
},
}),
controls: _patterns.get({
url : '/patterns/buttons/flat/pattern.html',
hooks : [{ title: 'login', id: 'login_button' }, { title: 'cancel', id: 'cancel_button' }]
}),
},
})
},
}).render();


Ви, напевно, зараз хотіли би сказати: умови в шаблоні реалізовані через жопу як-то дивно. Не поспішайте. Є два серйозних мотиву.

  • По-перше, маючи умови, визначені за замовчуванням (це ті що прикріплені до шаблону у вигляді окремого JS), ми отримуємо можливість перевизначити їх, не влізаючи в шаблон – через функцію візуалізації (як це показано вище). Таким чином нам не треба плодити купу компонентів, що відрізняються зовсім небагато, так як ми завжди можемо трохи «підправити» логіку під поточні потреби.
  • По-друге, і це для мене головне – у нас є можливість швидко перебудувати шаблон, якщо ввідні дані змінилися. Що б було зрозуміліше: звичайний шаблонизатор, аналізує умови і робить шаблон, який монтується в розмітку; якщо дані змінилися, потрібно видаляти отрисованный примірник і збирати шаблон заново. Підхід з функціями-умовами дозволяє обійтися без цієї дорогої операції і перезібрати лише невеликий шматок шаблону, до якого відноситься умова.


Щоб краще зрозуміти «по-друге» давайте змінимо шаблон рядка для нашої таблиці.

<tr>
<td>{{column_0}}{{::column_0}}</td>
<td>{{column_1}}{{::column_1}}</td>
<td>{{column_2}}{{::column_2}}</td>
<td>
<div>
<p>{{column_3}}{{::column_3}}</p>
<!--value_sets=0-->
<!--sub_value_sets=0-->
<p>This value is less than 111</p>
<!--sub_value_sets-->
<!--sub_value_sets=0.5-->
<p>This value is more than 111 and less than 222</p>
<!--sub_value_sets-->
<!--sub_value_sets=1-->
<p>This value is more than 222 and less than 333</p>
<!--sub_value_sets-->
<!--value_sets-->
<!--value_sets=0.5-->
<p>This value is more than 333 and less than 666</p>
<!--value_sets-->
<!--value_sets=1-->
<p>This value is more than 666 and less than 1000</p>
<!--value_sets-->
</div>
</td>
</tr>


Виглядає все дивно, правда? Але глянувши на функції-умови, стане все ясно.

var conditions = {
value_sets: function (data) {
if (data.column_3 <= 333 ) { return '0'; }
if (data.column_3 > 333 && data.column_3 <= 666 ) { return '0.5'; }
if (data.column_3 > 666 ) { return '1'; }
},
sub_value_sets: function (data) {
if (data.column_3 <= 111 ) { return '0'; }
if (data.column_3 > 111 && data.column_3 <= 222 ) { return '0.5'; }
if (data.column_3 > 222 ) { return '1'; }
},
};
conditions.value_sets. tracking = ['column_3'];
conditions.sub_value_sets. tracking = ['column_0'];
_conditions(conditions);


Насправді все просто: в залежності від числа, яке буде потрапляти в четверту клітинку кожного рядка, буде змінюватися і підпис під цим рядком.

Через властивість tracking ми показуємо patterns при зміні будь даних потрібно оновлювати шаблон. В даному випадку, ми прив'язали наші умови до значень першої і останньої клітинки кожного рядка.

Давайте запустимо рендеринг, додавши трохи динаміки.

var data_source = [];
for (var i = 0; i < 100; i += 1) {
data_source.push({
column_0: (Math.random() * 1000).toFixed(4),
column_1: (Math.random() * 1000).toFixed(4),
column_2: (Math.random() * 1000).toFixed(4),
column_3: (Math.random() * 1000).toFixed(4),
});
}
_patterns.get({
url : '/patterns/table/container/pattern.html',
node : document.body,
hooks : {
titles : {
column_0: 'Column #0',
column_1: 'Column #1',
column_2: 'Column #2',
column_3: 'Column #3',
},
rows : _patterns.get({
url: '/patterns/table/row_con/pattern.html',
hooks: data_source,
})
},
callbacks : {
success: function (results) {
(function (model) {
var fun = function () {
var r = Math.round(99 * Math.random()),
c = Math.round(3 * Math.random());
model.__rows__[r]['column_' + c] = (Math.random() * 1000).toFixed(4);
setTimeout(fun, Math.ceil(50 * Math.random()));
};
fun();
}(results.model));
}
}
}).render();


Отже, як ви бачите, через кожні 50 мс. ми міняємо значення клітинок. І якщо зміниться перша або остання шаблон буде перемальований в тій частині в якій необхідно, а не повністю, як це роблять багато інших шаблонизаторы (якщо взагалі роблять). Робочий приклад цього неподобства можна подивитися тут.

Замість завершення



Насправді є ще багато різних моментів, про які можна було б розповісти, але я боюся, що вже вийшов далеко за ліміт вашого терпіння, тому буду закруглятися.

До головних переваг patterns я б відніс наступне:

  • Завдяки тому, що шаблон – це всього лише HTML і ніякого нестандартного синтаксису не використовується, шаблони можна запускати окремо від сторінки і налагоджувати.
  • Завдяки нестандартному підходу до умов шаблон можна частково «перезібрати» без перезавантаження.
  • Завдяки вбудованій системі кешування весь шаблон (включаючи його ресурси) буде зберігатися на стороні клієнта, що знижує навантаження на трафік.


Але це головні переваги лише для мене, для вас вони можуть бути інші, або взагалі відсутні.

Тут ви зможете знайти досить детальний опис всього того, що відноситься до patterns.

сторінка проекту на github'е.

Спасибі за вашу увагу.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.