Шаблон — это заготовка (обычно строка HTML), которая путём подстановки значений (текст сообщения, цена и т.п.) превращается в сообщение/товар и т.п.
Шаблон позволяет вынести текст из скрипта и изложить его в более поддерживаемом виде.
Шаблонка «на строках»
Шаблонных систем для JavaScript много. Для начала рассмотрим одну из них, предложенную Джоном Ресигом и модифицированную в Underscore.js.
Шаблон состоит из текста, который, как правило, добавляют к HTML в блок SCRIPT с нестандартным type, например text/html:
<script *!*type="text/html"*/!* id="list-template">
<ul>
<% for (var i=1; i<=count; i++) { %>
<li><%=i%></li>
<% } %>
</ul>
</script>
Такие скрипты не выполняются, их содержимое игнорируется браузером, но доступно при помощи innerHTML. Шаблонная система читает его, а затем, получив данные, генерирует строку HTML.
Выглядит это, например, так: (запустите)
<script *!*type="text/html"*/!* id="list-template">
<ul>
<% for (var i=1; i<=count; i++) { %>
<li><%=i%></li>
<% } %>
</ul>
</script>
<!-- файл с кодом шаблонки -->
<script src="/files/tutorial/js/tmpl.js"></script>
<script>
*!*
// получить текст шаблона
var template = document.getElementById('list-template').innerHTML;
var compiled = tmpl(template); // скомпилировать
// получить результат, передав объект с данными {count:5}
var result = compiled( {count:5} );
document.write( result );
*/!*
</script>
Синтаксис шаблона
В шаблоне используются специальные разделители:
<% code %>- Код между разделителями
<% ... %>будет выполнен «как есть» <%= var %>- Переменная
varбудет вставлена на это место. Допустимо и выражение:<%= (x+1)*f(5) %>. <%- var_esc %>- Переменная
var_escбудет вставлена с экранированием. Например, еслиvar_esc = "<script>", то в шаблон попадёт<script>.
Почему использован именно нестандартный SCRIPT, а не DIV с display:none? Подумайте перед тем, как идти дальше..
Шаблонка изнутри
Запуск tmpl(строка) производит поиск и замену в строке шаблона, а затем создаёт из него JavaScript-функцию через new Function(obj, 'ТЕКСТ').
Текст функции создаётся динамически из строки и выглядит примерно так:
with(obj) { // obj - данные
var __p = [];
// для обычного текста:
__p.push("<ul>");
// код в <%...%> вставляется "как есть"
for (var i=1; i<=count; i++) {
// вставляется текст '<li>', затем переменная <%= i %> "как есть",
__p.push('<li>', i, </li>'); // затем опять текст '</li>'
}
__p.push("</ul>");
}
return __p.join('');
..То есть, делается массив __p, который будет содержать результат. В него добавляются (push) фрагменты HTML. Код копируется в функцию «как есть». Доступ к переменным шаблона обеспечивает with(obj) { .. }.
Как правило, такой шаблон работает достаточно быстро. Если один и тот же шаблон используется на многих страницах, то можно преобразовать его в функцию на сервере при помощи Node.JS и подключать JavaScript-файл с уже готовой функцией.
Никто не мешает компилировать строку шаблона на сервере.
Используя серверный JavaScript (Node.JS) можно собрать все шаблоны проекта, скомпилировать их и сохранить полученные функции в .js-файле, который уже подключать к HTML.
Есть данные:
var users = [
{name: "Вася", age: 10},
{name: "Петя", age: 15},
{name: "Женя", age: 20},
{name: "Маша", age: 25},
{name: "Даша", age: 30},
];
Выведите их в виде таблицы TABLE/TR/TD при помощи шаблонки.
Исходный документ с шаблонкой и данными: tutorial/browser/dom/template-grid-src/index.html
Шаблонка «на узлах»
Альтернативный подход к шаблонизации — это сделать DOM-узел, а потом клонировать его с потомками вызовом elem.cloneNode(true).
Например:
<!-- *!*шаблон*/!* -->
<div id="user-template" style="display:none">
<div>
Имя: <span class="user-name">Имя</span>
</div>
<div>
Возраст: <span class="user-age">Имя</span>
</div>
</div>
<script>
function makeUserNode(data) {
// /*!*клонировать и записать данные*/!*
var userTmpl = document.getElementById('user-template');
var userNode = userTmpl.cloneNode(true);
userNode.id = 'user-' + data.id;
userNode.querySelector('.user-name').innerHTML = data.name;
userNode.querySelector('.user-age').innerHTML = data.age;
userNode.style.display = '';
return userNode;
}
// *!*создать два узла из шаблона*/!*
var userNode = makeUserNode({ id: 1, name: 'Паша', age: 25 });
document.body.appendChild(userNode);
var userNode = makeUserNode({ id: 2, name: 'Василий', age: 29 });
document.body.appendChild(userNode);
</script>
Существенного различия в скорости, как правило, нет. Если интересно — сравнить скорость генерации шаблона user-template из примера выше вы можете в песочнице: tutorial/tmpl/bench.html.
Поэтому используется обычно то, что удобнее.
В частности, использование DOM требует, чтобы шаблон был узлом, а на строках нет таких ограничений — можно сгенерировать что угодно. Поэтому чаще применяются строки.
Использование шаблонок
Разберём ситуацию — JavaScript должен показать красивое диалоговое окно.
Чтобы описать его HTML-структуру, будем использовать шаблон:
<script type="text/html" id="dialog-tmpl">
<div class="dialog-window">
<div class="title">
<%=title%>
</div>
<div class="content">
<%=content%>
</div>
<button class="ok">OK</button>
</div>
</script>
При использовании шаблонки, мы получим из этого текста строку с подставленными значениями <%=title%> и <%=content%>. Этого недостаточно, чтобы окно работало: нужны обработчики событий.
Обработчики событий обычно ставятся на внешний элемент и работают через делегирование. Благодаря этому не нужно в сгенерированном HTML искать button с class="OK".
Точки прикрепления
Тем не менее, JS-коду бывают нужны создать ссылки на подэлементы, с которыми виджет планирует работать.
Они, как правило, записываются в переменные виджета при создании DOM. Иногда такие подэлементы называют «точками прикрепления», т.к. именно в этих местах JS-код связан с шаблоном.
Самое простое — при генерации DOM находить их по классу и прикреплять.
Пример ниже создаёт диалог и записывает элемент («точку прикрепления») с классом content в переменную, чтобы позже использовать её в методе setContent(content):
function Dialog() {
function init() {
// *!*создать окно dialog*/!*
var template= tmpl(document.getElementById('dialog-tmpl'));
var html = template({ title: "Диалог", content: "Нажмите кнопку" });
this.element = document.createElement('div');
this.element.innerHtml = html;
// *!*записать в переменную точку прикрепления */!*
this.contentElem = this.element.querySelector('.content');
}
*!*
// использовать точку прикрепления в методе
function setContent(content) {
this.contentElem.innerHTML = content;
}
*/!*
}
Здесь подразумевается, что доступен querySelector. Если у нас IE<8, то используем библиотеки для выборки (jQuery?) — на маленьком шаблоне они работают быстро, либо альтернативные способы навигации по DOM.
Автоприкрепление
В качестве универсального решения можно пометить все узлы, требующие прикрепления, атрибутом вида data-attach="property". Значение атрибута — имя свойство, в котором нужно сохранить ссылку на узел.
При генерации DOM из такого шаблона достаточно один раз найти узлы с таким атрибутом и расписать их по свойствам.
Пример шаблона:
<script type="text/html" id="dialog-tmpl">
<div class="dialog-window">
<div class="title">
<%=title%>
</div>
<div class="content" *!*data-attach="content"*/!*>
<%=content%>
</div>
<button class="ok">OK</button>
</div>
</script>
Код, который генерирует DOM и сохраняет .content в переменную:
function Dialog() {
var attachPoints = {};
function init() {
// *!*создать окно dialog*/!*
var template = tmpl(document.getElementById('dialog-tmpl'));
var html = template({ title: "Диалог", content: "Нажмите кнопку" });
this.element = document.createElement('div');
this.element.innerHtml = html;
attachToTemplate();
}
*!*
// прочитать точки прикрепления и сохранить в attachPoints
function attachToTemplate() {
var markedElems = this.element.querySelector('[data-attach]');
for (var i=0; i<markedElems.length; i++) {
var el = markedElems[i]
attachPoints[el.getAttribute('data-attach')] = el;
}
}
}
*/!*
// использовать точку прикрепления в методе
function setContent(content) {
*!*this.attachPoints.content.innerHTML = content;*/!*
}
}
Итого
Типы шаблонок:
- Шаблонки бывают «на строках» и «на узлах». Первые работают преобразованием строки в функцию, вторые — клонируют готовый узел.
- Используем тот тип шаблонки, который удобнее. Строки — более универсальны.
Применение шаблонок:
- Обработчики ставим на внешний элемент, используя делегирование.
- Точки прикрепления можно искать либо явно из JS-кода, разметив их классами, либо автоматически, выбрав для этого специальный атрибут, например
data-attach.
Функция tmpl, использованная в примерах: tmpl.js.
Комментарии
- Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
- Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
- Комментарии без смысла, с рекламой или не о статье вообще - удаляются.