Основы шаблонов
Шаблон — это текстовый файл, определяющий структуру, или внешний вид выходного файла, с предусмотренными позициями, в которые будут помещаться данные при отображении по шаблону (в Express шаблоны также называют представлениями).
Выбор шаблонов Express
В Express можно использовать много движков отображающих шаблонов ( template rendering engines). В этом руководстве для шаблонов будет использован Pug (ранее известный как Jade) . Это наиболее популярный в Node язык шаблонов, который о себе заявляет так: чистый, чувствительный к пробелам синтаксис для написания HTML, на который сильно повлиял Haml.
Разные языки шаблонов используют различные подходы для определения внешнего вида и разметки позиций для данных—некоторые используют HTML для определения внешнего вида, тогда как другие применяют различные форматы разметки, которые затем должны компилироваться в HTML. Pug — второго типа; он использует представление (representation) HTML, в котором первое слово в каждой строке обычно представляет элемент HTML, а отступы в следующих строках применяются, чтобы представить вложенные элементы. Результатом является определение страницы, которое транслируется непосредственно в HTML, и которое, вероятно, более краткое и легче читается.
Примечание: недостаток применения Pug — это чувствительность к отступам и пробелам (если добавить лишний пробел в «плохом» месте, можно получить невразумительный код ошибки). Однако, если ваши шаблоны уже действуют, их очень легко читать и поддерживать.
Конфигурация шаблона
Когда создавался каркас (the skeleton website) веб-сайта LocalLibrary, он был настроен на использование Pug . Можно было заметить, что модуль pug включён в зависимости в файле package.json, и установлен (app.set(. )) как движок представлений в файле app.js. Эта установка показывает,, что движок представлений — pug, и что Express должен искать шаблоны в подкаталоге /views.
// View engine setup. app.set("views", path.join(__dirname, "views")); app.set("view engine", "pug");
Если посмотреть содержимое каталога views, можно увидеть файлы с расширением .pug, в которых шаблоны представлений по умолчанию. Это представление для домашней страницы (index.pug) и базовый шаблон (layout.pug), который следует заменить нашим содержимым.
/express-locallibrary-tutorial //the project root /views error.pug index.pug layout.pug
Синтаксис шаблонов
Пример файла шаблона (ниже) демонстрирует многие наиболее полезные черты Pug.
Сначала отметим, что файл отражает структуру типового HTML-файла, причём первое слов в (почти) каждой строке является элементом HTML, а отступы используются, чтобы показать вложенные элементы. Так, например, элемент body находится внутри элемента html , а элементы p (параграфы) — внутри элемента body, и так далее. Невложенные элементы (т.е. индивидуальные параграфы) располагаются в отдельных строках.
doctype html html(lang="en") head title= title script(type='text/javascript'). body h1= title p This is a line with #[em some emphasis] and #[strong strong text] markup. p This line has un-escaped data: ! is emphasised'> and escaped data: # is not emphasised'>. | This line follows on. p= 'Evaluated and escaped expression:' + title // You can add single line JavaScript comments and they are generated to HTML comments //- Introducing a single line JavaScript comment with "//-" ensures the comment isn't rendered to HTML p A line with a link a(href='/catalog/authors') Some link text | and some extra text. #container.col if title p A variable named "title" exists. else p A variable named "title" does not exist. p. Pug is a terse and simple template language with a strong focus on performance and powerful features. h2 Generate a list ul each val in [1, 2, 3, 4, 5] li= val
Атрибуты элементов определены в скобках после соответствующих элементов. В скобках располагается список пар *имя атрибута=значение,*причём элементы списка разделяются запятой или пробелом. Например:
- script(type=’text/javascript’) , link(rel=’stylesheet’, href=’/stylesheets/style.css’)
- meta(name=’viewport’ content=’width=device-width initial-scale=1′)
Значения всех атрибутов экранируются (т.е. такие символы как » > » заменяются эквивалентными кодами HTML как » >» ) , чтобы предотвратить JavaScript инъекции и межсайтовые атаки.
Если после тэга стоит знак = , следующий текст рассматривается как выражение JavaScript. Например, ниже в первой строке, содержимое тэга h1 будет переменной title (которая определена в файле или передана в шаблон из Express). Во второй строке содержимое параграфа — это текстовая строка, соединённая с переменной title . В каждом из случаев поведение по умолчанию — экранировать строки.
h1= title p= 'Evaluated and escaped expression:' + title
Если после тэга знак = отсутствует, тогда содержимое рассматривается как обычный текст. Внутри текста можно вставить экранированные или неэкранированные данные, применяя синтаксис #<> и !<> , как показано ниже. В простой текст можно также вставлять «сырой» HTML.
p This is a line with #[em some emphasis] and #[strong strong text] markup. p This line has an un-escaped string: ! is emphasised'>, an escaped string: # is not emphasised'>, and escaped variables: #.
Примечание: Почти всегда желательно экранировать данные, полученные от пользователей (при помощи синтаксиса #<> ). Данные, которым можно верить (т.е. подсчитанное количество записей, могут быть выведены без экранирования значений.
Можно использовать символ конвейера (‘|‘) в начале строки, чтобы отметить простой текст («plain text»). Например, дополнительный текст, приведённый ниже, будет показан в той же строке, что и предыдущий, но не будет относиться к ссылке.
a(href='http://someurl/') Link text | Plain text
Pug позволяет выполнять условные операции if , else , else if и unless — пример приведён ниже:
if title p Переменная с именем "title" существует else p Переменной с именем "title" не существует
Можно также выполнять циклы (итерации), применяя синтаксис each-in или while . Фрагмент кода (ниже) содержит цикл по элементам массива, чтобы показать список элементов (отметим применение ‘li=’ для оценки «val» как переменной). Значение итератора val может быть также передано в шаблон как переменная!
ul each val in [1, 2, 3, 4, 5] li= val
Синтаксис разрешает также комментарии (которые попадут в результат или нет, по вашему желанию), смеси для создания повторно используемых блоков кода, операторы выбора case, и много другого. Более подробная информация — в документации The Pug docs.
Расширение шаблонов
Принято иметь общую структуру для всех страниц сайта, включая стандартную HTML-разметку для заголовка, футера, навигации и т.д. Вместо того, чтобы заставлять разработчиков дублировать эти образцы на каждой странице, Pug позволяет объявить базовой шаблон, а затем модифицировать его, заменяя только те небольшие части, которые различны на каждой конкретной странице.
Например, базовый шаблон layout.pug, созданный в каркасе проекта, имеет такой вид:
doctype html html head title= title link(rel='stylesheet', href='/stylesheets/style.css') body block content
Тэг block применён для отметки разделов контента, которые могут быть заменены в производных шаблона (если блок не переопределяется, будет использования его реализация в базовом классе).
Умолчание для index.pug (созданный для каркаса проекта) показывает, как можно заменить базовый шаблон. Тэг extends идентифицирует базовый шаблон, который следует использовать, а затем мы используем block section_name, чтобы отметить новый контент раздела, который мы заменяем.
extends layout block content h1= title p Welcome to #
Next steps
- Return to Express Tutorial Part 5: Displaying library data.
- Proceed to the next subarticle of part 5: The LocalLibrary base template.
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on 3 авг. 2023 г. by MDN contributors.
Your blueprint for a better internet.
1. Введение
Шаблонизация (templating) — метод связывания данных и разметки. Удобный способ генерации HTML по шаблону и данным. Используется на клиенте и сервере.
Суть шаблонизации заключается в том, чтобы отделить описание HTML от логики. Разметка помещается в отдельные файлы (шаблоны), а в местах, где необходимо вывести данные, размещаются специальные псевдопеременные. JS-код загружает нужный шаблон и заменяет в нем псевдопеременные на соответствующие данные.
На обычных сайтах, где HTML и CSS уже есть, элементы интерфейса получают готовый DOM и используя JavaScript вешают обработчики, оживляя его. Но в сложных интерфейсах разметка изначально отсутствует на странице. DOM создается с помощью JavaScript-кода динамически на основе данных, полученных с сервера или из других источников.
Представьте список постов на веб-странице, а так же любые другие ситуации, которые требуют отображения коллекции однотипных элементов, но с разными данными.
Данные для постов приходят от сервера как массив объектов, у нас есть шаблон одного поста. Используя цикл, мы можем пройтись по массиву объектов и вызвать функцию-шаблон для каждого объекта, результатом будет строка с подставленными данными. После чего мы в контейнер для постов просто повесим строку и браузер создаст разметку.
Это — стандартный подход написания динамических элементов интерфейса, данные для которых изменяются со временем.
1.1. Использование шаблонизации
Весь процесс требует нескольких простых шагов:
- Наличие данных, которыми будет наполнятся элемент интерфейса
- Шаблон, по которому будет составлена разметка элемента
- Библиотека, которая предоставляет средства шаблонизации
Мы подробно рассмотрим это на примерах, но по сути это очень просто:
- Подключить в проект выбранную библиотеку для шаблонизации
- Составить HTML-шаблон необходимого вида
- Сделать выборку шаблона в JS-файле
- Произвести рендеринг шаблона вместе с данными
2. Шаблон
Шаблон — это строка в специальном формате, которая путём подстановки значений и выполнения встроенных фрагментов кода превращается в HTML.
Синтаксис шаблона зависит от библиотеки, самое важное — это понимать принцип работы шаблонизаторов. Мы будем использовать библиотеку Handlebars.
2.1. Синтаксис
Шаблон — это строка со специальными разделителями, которых в Handlebars всего три:
div>>div> div><>div> div>>>div>
То есть синтаксис Handlebars — это обычный HTML, с вставками вида > . Именно в те места, где указаны вставки, будут помещены данные.
div class="menu"> h3 class="menu-title">>h3> ul class="menu-list"> > li class="menu-item">>li> > ul> div>
2.2. Методы хранения
Шаблон — это просто многострочный HTML-текст, ему не место в файле скриптов. Один из способов — записать его в HTML-файле в тег template .
Более современный способ, например при использовании Webpack , хранить шаблон в отдельном файле и импортировать его. Для этого в конфигурацию Webpack нужно добавить загрузчик Handlebars-шаблонов.
Внешние шаблоны имеют много преимуществ, главным образом в том, что шаблоны никогда не будут загружаться клиенту, если они не нужны на странице. Для усвоения ключевых концепций будем пользоваться встроенными шаблонами.
Давайте обернем шаблон в тег template и дадим ему уникальный идентификатор, чтобы можно было выбрать в JS-файле по селектору.
template id="menu-template"> div class="menu"> h3 class="menu-title">>h3> ul class="menu-list"> > li class="menu-item">>li> > ul> div> template>
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>
3.2. Составить HTML шаблон
Этот шаг мы выполнили выше и у нас уже есть полностью готовый шаблон для меню. Добавим его перед всеми скриптами в документе.
body> template id="menu-template"> div class="menu"> h3 class="menu-title">>h3> ul class="menu-list"> > li class="menu-item">>li> > 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>
3.3. Сделать выборку шаблона в скрипте
template — это тег, значит мы можем выбрать его по селектору тега/класса/идентификатора. Хорошей практикой считается давать тегам template , содержащим шаблон, идентификаторы, так как они уникальны.
Теперь в JS-файле мы можем по id выбрать сам тег template и его контент в виде строки, для этого используем свойство innerHTML .
const source = document.querySelector('#menu-template').innerHTML.trim();
3.4. Отрендерить шаблон с данными
У нас уже есть библиотека и шаблон из которого мы изъяли текстовый контент. Для работы с шаблоном в библиотеке Handlebars есть функция compile . Эта функция запускает компиляцию шаблона source и возвращает результат в виде функции, которую далее можно запустить с данными и получить строку-результат.
Handlebars.compile(source)
Вызов Handlebars.compile(source) разбивает HTML-строку по разделителям и при помощи new Function создаёт на её основе функцию. Тело этой функции создаётся таким образом, что код, который в шаблоне оформлен как > , попадает в неё как есть, а переменные и текст прибавляются к специальному временному буферу, который в итоге возвращается.
const source = document.querySelector('#menu-template').innerHTML.trim(); const template = Handlebars.compile(source);
Теперь используя функцию-шаблон template можем передать ей данные как аргумент и она вернет HTML-строку.
Для начала добавим данные для списка. В шаблоне указаны какие-то переменные title и items .
В реальных задачах сначала создаются форматы для данных, а потом под них пишутся шаблоны, но для наглядности мы для шаблона напишем данные.
Данные для шаблона — это что угодно, строка, объект, массив и т.д., зависит от задачи, чаще всего — объект или массив объектов. Так как у нас список с заголовком и набором пунктов, нам удобно использовать объект такого вида.
const menuData = title: 'Eat it createElement, templates rule!', items: ['Handlebars', 'LoDash', 'Pug', 'EJS', 'lit-html'], >;
Следующим шагом будет вызвать функцию-шаблон и передать ей menuData как аргумент. В результате получим строку с подставленными значениями, поместим ее в тег и браузер, распарсив ее, создаст HTML-разметку.
4. Шаблоны и Webpack
Когда используем сборщик модулей, очень удобно работать со внешними шаблонами.
npm install handlebars
npm install --save-dev handlebars-loader
Обновляем конфигурацию Webpack, добавляя настройки загрузчика.
// В webpack.config.js . module: rules: [ . test: /\.hbs$/, exclude: /node_modules/, use: "handlebars-loader" > ] > >
В папке src создаем папку templates , в которой добавляем файлы шаблонов с расширением .hbs . Для нашего меню это будет menu.hbs . Помещаем разметку шаблона в файл, без тега template .
div class="menu"> h3 class="menu-title">>h3> ul class="menu-list"> > li class="menu-item">>li> > ul> div>
Там, где хотим использовать шаблон, импортируем файл с шаблоном. Особенность в том, что при импорте, 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 разметка с подставленным значениями
5. Дополнительные материалы
- Learn Handlebars in 10 Minutes or Less
- A Beginner’s Guide to Handlebars
- Правильная шаблонизация
Новый тег : введение стандарта шаблонизации на стороне клиента
Понятие шаблонизации в веб-разработке не является чем-то новым. Более того, серверные языки шаблонов и шаблонизаторы вроде Django (Python), ERB/Haml (Ruby), и Smarty (PHP) существуют уже далеко не первый день. Последние несколько лет можно наблюдать волну возникновения MVC-фреймворков. Они все немного отличаются друг от друга, но в большинстве своем в их основе лежит общий механизм воспроизведения слоя представления: шаблоны.
Посмотрим правде в глаза. Шаблоны — это прекрасно. Не стесняйтесь, порасспрашивайте мнение коллег на их счет. Даже само определение шаблона оставляет тёплые и приятные чувства:
шаблон (сущ.) — документ или файл с заданным форматом, который используется в качестве отправной точки для определённой цели, чтобы избежать необходимости воссоздания формата при повторном использовании.
«…чтобы избежать необходимости воссоздания формата при повторном использовании…» Не знаю как вы, но я предпочитаю избегать лишней работы. Почему же в веб-платформе отсутствует встроенная поддержка того, что очевидно является таким важным для разработчиков?
Спецификация для HTML-шаблонов от W3C должна заполнить этот пробел. В ней определён новый элемент , который является реализацией стандарта шаблонизации для DOM на стороне клиента. Шаблоны позволяют объявлять фрагменты разметки, которые парсятся как HTML, игнорируются при загрузке страницы, но могут быть инстанциированы позже. Цитата от Рафаеля Вайнштайна (Rafael Weinstein) (автора спецификации):
«Они обозначают место, куда можно поместить большой кусок HTML, который вы хотите оградить от какого-либо влияния со стороны браузера…какой бы ни была причина для этого»
Как определить поддерживается ли элемент?
Для выявления поддержки , создайте объект DOM и проверьте наличие свойства .content :
function supportsTemplate( ) < return 'content' in document.createElement('template'); > if (supportsTemplate()) < // Всё в норме. > else < // Используйте старые приёмы или библиотеки шаблонизации >
Объявление содержимого шаблона
Элемент представляет шаблон. В него помещено «содержимое шаблона»; по сути инертные куски DOM, которые можно использовать многократно. Шаблоны можно рассматривать как фрагменты скаффолдинга, которые можно многократно использовать в приложении.
Чтобы создать шаблонный контент, напишите код разметки и оберните его в элемент :
template id="mytemplate"> img src="" alt="красивая картинка"> div class="comment"> div> template>
Наблюдательный читатель должно быть заметил что изображение пустое. Это нас вполне устраивает и было сделано преднамеренно. Мы не получим ошибку 404 или ошибки в консоли потому, что ссылка на изображение битая, так как изображение не будет вызвано при загрузке страницы. Позже можно динамически сгенерировать URL-адрес изображения. Читайте основные принципы.
Основные принципы
Помещение содержимого в даёт нам несколько важных свойств:
- Содержимое фактически инертно, пока его не активировать. По сути, соответствующая разметка спрятана и не воспроизводится.
- Содержимое шаблона не может привести к каким-либо побочным эффектам. Скрипты не выполняются, изображения не загружаются, аудио не проигрывается… пока шаблон не активирован.
- Содержимое шаблона не считается частью страницы. Использование document.getElementById() или querySelector() на странице не возвратит дочерние элементы шаблона.
- Шаблоны можно помещать куда угодно: в , или ; и помещать в них любой тип содержимого, который может располагаться в этих частях страницы. Обратите внимание что «куда угодно» значит что можно без проблем использовать в местах, запрещённых парсером HTML… всех кроме дочерних элементов модели содержимого.
table> tr> template id="cells-to-repeat"> td>какое-то содержимое td> template> tr> table>
Активация шаблона
Чтобы использовать шаблон, нужно его активировать. Иначе его содержимое не будет воспроизводиться. Наиболее простой способ — это создать глубокую копию его содержимого .content используя cloneNode() . .content — это неизменимое свойство, которое обозначает фрагмент документа с содержимым шаблона.
var t = document.querySelector('#mytemplate'); // Во время выполнения заполняем src. t.content.querySelector('img').src = 'logo.png'; document.body.appendChild(t.content.cloneNode(true));
После извлечения шаблона, его содержимое начинает функционировать. В этом конкретном примере, содержимое копируется, выполняется запрос изображения и воспроизводится конечная разметка.
Демо
Пример: инертный скрипт
В этом примере продемонстрировано бездействие содержимого шаблона. выполняется только после нажатия на кнопку и извлечения шаблона.
button onclick="useIt()">Нажми на меня button> div id="container"> div> script> function useIt( ) < var content = document.querySelector('template').content; // Обновление чего-нибудь в DOM шаблона. var span = content.querySelector('span'); span.textContent = parseInt(span.textContent) + 1; document.querySelector('#container').appendChild( content.cloneNode(true)); > script> template> div>Количество раз, которое использован шаблон: span>0 span> div> script>alert('Спасибо!') script> template>
Нажми на меня
Количество раз, которое использован шаблон: 0
Пример: Создание теневого дерева из шаблона
Большинство разработчиков прикрепляет теневое дерево к ведущему элементу изменяя строку разметки через .innerHTML :
div id="host"> div> script> var shadow = document.querySelector('#host').webkitCreateShadowRoot(); shadow.innerHTML = 'Ведущий элемент'; script>
Проблема такого подхода состоит в том, что чем сложнее становится ваш теневой DOM, тем чаще вам приходится прибегать к конкатенации строк. Он не масштабируется, очень быстро получается путаница, все в печали. Благодаря именно таким подходам возник межсайтовый скриптинг! приходит на помощь.
Более разумным было бы напрямую присоединять содержимое шаблона к корневому элементу теневого дерева:
template> style> @host < * < background: #f8f8f8; padding: 10px; -webkit-transition: all 400ms ease-in-out; box-sizing: border-box; border-radius: 5px; width: 450px; max-width: 100%; > *:hover < background: #ccc; > > div < position: relative; > header < padding: 5px; border-bottom: 1px solid #aaa; > h3 < margin: 0 !important; > textarea < font-family: inherit; width: 100%; height: 100px; box-sizing: border-box; border: 1px solid #aaa; > footer < position: absolute; bottom: 10px; right: 5px; > style> div> header> h3>Добавление комментария h3> header> content select="p"> content> textarea> textarea> footer> button>Опубликовать button> footer> div> template> div id="host"> p>Здесь должны быть инструкции p> div> script> var shadow = document.querySelector('#host').webkitCreateShadowRoot(); shadow.appendChild(document.querySelector('template').content); script>
Инструкции для пользователя
Нюансы
Вот несколько нюансов, с которыми я столкнулся используя в полевых условиях:
- Используя модуль modpagespeed, берегитесь этой ошибки. CSS-правила PageSpeed могут переместить шаблоны, в которых определяется строчный , в шапку.
- Предварительный запуск шаблона невозможен, это значит что нельзя предварительно загрузить ресурсы, выполнить JS, загрузить исходный CSS, и т.д. Это касается и стороны сервера, и клиента. Шаблон воспроизводится только когда он активирован.
- Будьте осторожны с вложенными шаблонами. Они ведут себя не так, как вы можете ожидать.
template> ul> template> li>Всякая всячина li> template> ul> template>
Активация внешнего шаблона не означает активацию внутренних. То есть, во вложенных шаблонах дочерние шаблоны должны быть активированы вручную.
Путь к стандарту
Не стоит забывать с чего всё начиналось. Путь к стандартизации HTML-шаблонов был долгим. На протяжении многих лет мы придумывали ловкие способы создания шаблонов многократного использования. Ниже представлены два из них, с которыми столкнулся я. Они представлены в этой статье для сравнения.
Метод 1: Скрытый DOM
Один из подходов, который использовался разработчиками продолжительное время предусматривает создание «скрытого» DOM, который не отображается благодаря атрибуту hidden или display:none .
div >"mytemplate" hidden> img src="logo.png"> div >"comment">