Шаблонизация

1. Введение

Шаблонизация (templating) — метод связывания данных и разметки. Удобный способ генерации HTML по шаблону и данным. Используется на клиенте и сервере.

Суть шаблонизации заключается в том, чтобы отделить описание HTML от логики. Разметка помещается в отдельные файлы (шаблоны), а в местах, где необходимо вывести данные, размещаются специальные псевдопеременные. JS-код загружает нужный шаблон и заменяет в нем псевдопеременные на соответствующие данные.

На обычных сайтах, где HTML и CSS уже есть, элементы интерфейса получают готовый DOM и используя JavaScript вешают обработчики, оживляя его. Но в сложных интерфейсах разметка изначально отсутствует на странице. DOM создается с помощью JavaScript-кода динамически на основе данных, полученных с сервера или из других источников.

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

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

Это - стандартный подход написания динамических элементов интерфейса, данные для которых изменяются со временем.

1.1. Использование шаблонизации

Весь процесс требует нескольких простых шагов:

  • Наличие данных, которыми будет наполнятся элемент интерфейса

  • Шаблон, по которому будет составлена разметка элемента

  • Библиотека, которая предоставляет средства шаблонизации

templating

Мы подробно рассмотрим это на примерах, но по сути это очень просто:

  • Подключить в проект выбранную библиотеку для шаблонизации

  • Составить HTML-шаблон необходимого вида

  • Сделать выборку шаблона в JS-файле

  • Произвести рендеринг шаблона вместе с данными

2. Шаблон

Шаблон — это строка в специальном формате, которая путём подстановки значений и выполнения встроенных фрагментов кода превращается в HTML.

Синтаксис шаблона зависит от библиотеки, самое важное - это понимать принцип работы шаблонизаторов. Мы будем использовать библиотеку Handlebars.

2.1. Синтаксис

Шаблон - это строка со специальными разделителями, которых в Handlebars всего три:

<!--
  Выражение expr между разделителями будет выполнено как есть,
  то есть вместо имени переменной будет подставлено ее значение,
  при этом если переменная содержит строку с тегами, то не произойдет
  парса строки.
-->
<div>{{expr}}</div>

<!--
  Используется для вставки HTML-тегов, то есть выражение будет
  распарсеной браузером на наличие тегов.
-->
<div>{{{expr}}</div>

<!--
  Используется для встроенных и кастомных операций, к примеру #each
  работает как forEach, перебирая коллекцию, а в теле each к
  элементу коллекции можно обратиться как this. А #if можно использовать
  для ветвлений.
-->
<div>{{#helper}}{{/helper}}</div>
Copy

То есть синтаксис Handlebars - это обычный HTML, с вставками вида {{...}}. Именно в те места, где указаны вставки, будут помещены данные.

<div class="menu">
  <h3 class="menu-title">{{title}}</h3>
  <ul class="menu-list">
    {{#each items}}
    <li class="menu-item">{{this}}</li>
    {{/each}}
  </ul>
</div>
Copy

2.2. Методы хранения

Шаблон - это просто многострочный HTML-текст, ему не место в файле скриптов. Один из способов — записать его в HTML-файле в тег template.

Более современный способ, например при использовании Webpack, хранить шаблон в отдельном файле и импортировать его. Для этого в конфигурацию Webpack нужно добавить загрузчик Handlebars-шаблонов.

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

Давайте обернем шаблон в тег template и дадим ему уникальный идентификатор, чтобы можно было выбрать в JS-файле по селектору.

<template id="menu-template">
  <div class="menu">
    <h3 class="menu-title">{{title}}</h3>
    <ul class="menu-list">
      {{#each items}}
      <li class="menu-item">{{this}}</li>
      {{/each}}
    </ul>
  </div>
</template>
Copy

3. Использование шаблона

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

3.1. Добавить в проект библиотеку

Подключение библиотеки в проект происходит очень просто. Есть несколько вариантов.

  • Скачать файл библиотеки и подключить его в index.html

  • Использовать CDN-сервис для получения ссылки на файл библиотеки

  • Если используется инструмент вроде Webpack, можно ставить библиотеку как npm-пакет

Пока что используем CDN. В разделе документации о установке библиотеки есть ссылка на CDN-сервис на котором можно скопировать необходимый URL. Это - минифицированая версия библиотеки. Все что нужно сделать — это добавить еще один тег script перед нашим файлом скриптов и перед закрывающим тегом body в index.html.

<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.11/handlebars.min.js"></script>
Copy

3.2. Составить HTML шаблон

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

<body>
  <!-- html разметка -->

  <template id="menu-template">
    <div class="menu">
      <h3 class="menu-title">{{title}}</h3>
      <ul class="menu-list">
        {{#each items}}
        <li class="menu-item">{{this}}</li>
        {{/each}}
      </ul>
    </div>
  </template>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.11/handlebars.min.js"></script>
  <script src="js/scripts.js"></script>
</body>
Copy

3.3. Сделать выборку шаблона в скрипте

template - это тег, значит мы можем выбрать его по селектору тега/класса/идентификатора. Хорошей практикой считается давать тегам template, содержащим шаблон, идентификаторы, так как они уникальны.

Теперь в JS-файле мы можем по id выбрать сам тег template и его контент в виде строки, для этого используем свойство innerHTML.

const source = document.querySelector('#menu-template').innerHTML.trim();
Copy

Ссылка на пример в Codepen.io

3.4. Отрендерить шаблон с данными

У нас уже есть библиотека и шаблон из которого мы изъяли текстовый контент. Для работы с шаблоном в библиотеке Handlebars есть функция compile. Эта функция запускает компиляцию шаблона source и возвращает результат в виде функции, которую далее можно запустить с данными и получить строку-результат.

Handlebars.compile(source)
Copy

Вызов Handlebars.compile(source) разбивает HTML-строку по разделителям и при помощи new Function создаёт на её основе функцию. Тело этой функции создаётся таким образом, что код, который в шаблоне оформлен как {{...}}, попадает в неё как есть, а переменные и текст прибавляются к специальному временному буферу, который в итоге возвращается.

const source = document.querySelector('#menu-template').innerHTML.trim();
const template = Handlebars.compile(source);
Copy

Ссылка на пример в Codepen.io

template compilation

Теперь используя функцию-шаблон template можем передать ей данные как аргумент и она вернет HTML-строку.

Для начала добавим данные для списка. В шаблоне указаны какие-то переменные title и items.

В реальных задачах сначала создаются форматы для данных, а потом под них пишутся шаблоны, но для наглядности мы для шаблона напишем данные.

Данные для шаблона - это что угодно, строка, объект, массив и т.д., зависит от задачи, чаще всего - объект или массив объектов. Так как у нас список с заголовком и набором пунктов, нам удобно использовать объект такого вида.

const menuData = {
  title: 'Eat it createElement, templates rule!',
  items: ['Handlebars', 'LoDash', 'Pug', 'EJS', 'lit-html'],
};
Copy

Следующим шагом будет вызвать функцию-шаблон и передать ей menuData как аргумент. В результате получим строку с подставленными значениями, поместим ее в тег и браузер, распарсив ее, создаст HTML-разметку.

Ссылка на пример в Codepen.io

4. Шаблоны и Webpack

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

Добавляем библиотеку.

npm install handlebars
Copy

Добавляем загрузчик handlebars-loader.

npm install --save-dev handlebars-loader
Copy

Обновляем конфигурацию Webpack, добавляя настройки загрузчика.

// В webpack.config.js
{
  ...
  module: {
    rules: [
      ...
      { test: /\.hbs$/, exclude: /node_modules/, use: "handlebars-loader" }
    ]
  }
}
Copy

В папке src создаем папку templates, в которой добавляем файлы шаблонов с расширением .hbs. Для нашего меню это будет menu.hbs. Помещаем разметку шаблона в файл, без тега template.

<!-- В menu.hbs -->
<div class="menu">
  <h3 class="menu-title">{{title}}</h3>
  <ul class="menu-list">
    {{#each items}}
    <li class="menu-item">{{this}}</li>
    {{/each}}
  </ul>
</div>
Copy

Там, где хотим использовать шаблон, импортируем файл с шаблоном. Особенность в том, что при импорте, handlebars-loader обработает файл шаблона и в menuTemplate уже будет лежать скомпилированная функция-шаблон готовая к использованию.

// В app.js
import menuTemplate from '/path/to/templates/menu.hbs';

const menuData = {
  title: 'Eat it createElement, templates rule!',
  items: ['Handlebars', 'LoDash', 'Pug', 'EJS', 'lit-html'],
};

const markup = menuTemplate(menuData); // html разметка с подставленным значениями
Copy

5. Дополнительные материалы

Last updated