«1. Обзор

В этом руководстве мы покажем, как использовать фрагменты Thymeleaf для повторного использования некоторых общих частей сайта. После настройки очень простого проекта Spring MVC мы сосредоточимся на представлениях.

Если вы новичок в Thymeleaf, вы можете проверить другие статьи на этом сайте, такие как это введение, а также эту о версии движка 3.0.

2. Зависимости Maven

Нам понадобится пара зависимостей, чтобы включить Thymeleaf:

<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf</artifactId>
    <version>3.0.11.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    <version>3.0.11.RELEASE</version>
</dependency>

Последнюю версию тимелеафа и тимелеаф-спринг5 можно найти на Maven Central.

3. Весенний проект

3.1. Конфигурация Spring MVC

Чтобы включить Thymeleaf и установить суффикс шаблона, нам нужно настроить MVC с преобразователем представления и преобразователем шаблона.

Мы также установим каталог для некоторых статических ресурсов:

@Bean
public ViewResolver htmlViewResolver() {
    ThymeleafViewResolver resolver = new ThymeleafViewResolver();
    resolver.setTemplateEngine(templateEngine(htmlTemplateResolver()));
    resolver.setContentType("text/html");
    resolver.setCharacterEncoding("UTF-8");
    resolver.setViewNames(ArrayUtil.array("*.html"));
    return resolver;
}

private ITemplateResolver htmlTemplateResolver() {
    SpringResourceTemplateResolver resolver
      = new SpringResourceTemplateResolver();
    resolver.setApplicationContext(applicationContext);
    resolver.setPrefix("/WEB-INF/views/");
    resolver.setCacheable(false);
    resolver.setTemplateMode(TemplateMode.HTML);
    return resolver;
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/resources/**", "/css/**")
      .addResourceLocations("/WEB-INF/resources/", "/WEB-INF/css/");
}

Обратите внимание, что если мы используем Spring Boot, эта конфигурация может не понадобиться, если только нам не нужно применить наши собственные настройки.

3.2. Контроллер

В этом случае контроллер — это просто средство для представлений. Каждое представление показывает другой сценарий использования фрагмента.

Последний загружает некоторые данные, которые передаются через модель для отображения в представлении:

@Controller
public class FragmentsController {

    @GetMapping("/fragments")
    public String getHome() {
        return "fragments.html";
    }

    @GetMapping("/markup")
    public String markupPage() {
        return "markup.html";
    }

    @GetMapping("/params")
    public String paramsPage() {
        return "params.html";
    }

    @GetMapping("/other")
    public String otherPage(Model model) {
        model.addAttribute("data", StudentUtils.buildStudents());
        return "other.html";
    }
}

Обратите внимание, что имена представлений должны содержать суффикс «.html» из-за того, как мы настроили наш резольвер. Мы также будем указывать суффикс при обращении к именам фрагментов.

4. Представления

4.1. Простое включение фрагментов

Прежде всего, мы будем повторно использовать общие части на наших страницах.

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

Существует три основных способа включения содержимого из фрагмента:

    вставка — вставляет содержимое внутри тега replace — заменяет текущий тег тегом, определяющим включение фрагмента — это устарело, но может по-прежнему появляться в унаследованном коде

В следующем примере, fragments.html, показано использование всех трех способов. Этот шаблон Thymeleaf добавляет фрагменты в заголовок и тело документа:

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Thymeleaf Fragments: home</title>
<!--/*/ <th:block th:include="fragments/general.html :: headerfiles">
        </th:block> /*/-->
</head>
<body>
    <header th:insert="fragments/general.html :: header"> </header>
    <p>Go to the next page to see fragments in action</p>
    <div th:replace="fragments/general.html :: footer"></div>
</body>
</html>

Теперь давайте посмотрим на страницу, содержащую некоторые фрагменты. Он называется general.html и представляет собой целую страницу с некоторыми частями, определенными как готовые к использованию фрагменты:

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="headerfiles">
<meta charset="UTF-8" />
<link th:href="@{/css/styles.css}" rel="stylesheet">
</head>
<body>
    <div th:fragment="header">
        <h1>Thymeleaf Fragments sample</h1>
    </div>
    <p>Go to the next page to see fragments in action</p>
    <aside>
        <div>This is a sidebar</div>
    </aside>
    <div class="another">This is another sidebar</div>
    <footer th:fragment="footer">
        <a th:href="@{/fragments}">Fragments Index</a> | 
        <a th:href="@{/markup}">Markup inclussion</a> | 
        <a th:href="@{/params}">Fragment params</a> | 
        <a th:href="@{/other}">Other</a>
    </footer>
</body>
</html>

Раздел \u003chead\u003e содержит только таблицу стилей, но мы могли бы применить другие инструменты, такие как Bootstrap, jQuery или Foundation, либо напрямую, либо с помощью Webjars.

Обратите внимание, что все повторно используемые теги этого шаблона имеют атрибут th:fragment, но далее мы увидим, как включить любую другую часть страницы.

После рендеринга и включения фрагментов возвращаемый контент:

<!DOCTYPE HTML>
<html>
<head>
<title>Thymeleaf Fragments: home</title>
<meta charset="UTF-8" />
<link href="/spring-thymeleaf/css/styles.css" rel="stylesheet">
</head>
<body>
    <header>
        <div>
            <h1>Thymeleaf Fragments sample</h1>
        </div>
    </header>
    <p>Go to the next page to see fragments in action</p>
    <footer>
        <a href="/spring-thymeleaf/fragments">Fragments Index</a> | 
        <a href="/spring-thymeleaf/markup">Markup inclussion</a> | 
        <a href="/spring-thymeleaf/params">Fragment params</a> | 
        <a href="/spring-thymeleaf/other">Other</a>
    </footer>
</body>
</html>

4.2. Селекторы разметки для фрагментов

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

Эта страница, например, включает некоторые компоненты из файла general.html: блок aside и блок div.another:

<body>
    <header th:insert="fragments/general.html :: header"> </header>
    <div th:replace="fragments/general.html :: aside"></div>
    <div th:replace="fragments/general.html :: div.another"></div>
    <div th:replace="fragments/general.html :: footer"></div>
</body>

4.3. Параметризованные фрагменты

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

В этом примере мы определяем фрагмент для универсального поля формы:

<div th:fragment="formField (field, value, size)">
    <div>
        <label th:for="${#strings.toLowerCase(field)}"> <span
            th:text="${field}">Field</span>
        </label>
    </div>
    <div>
        <input type="text" th:id="${#strings.toLowerCase(field)}"
            th:name="${#strings.toLowerCase(field)}" th:value="${value}"
            th:size="${size}">
    </div>
</div>

А вот простое использование этого фрагмента, в котором мы передаем ему параметры:

<body>
    <header th:insert="fragments/general.html :: header"> </header>
    <div th:replace="fragments/forms.html
      :: formField(field='Name', value='John Doe',size='40')">
    </div>
    <div th:replace="fragments/general.html :: footer"></div>
</body>

Вот как возвращается поле будет иметь вид:

<div>
    <div>
        <label for="name"> <span>Name</span>
        </label>
    </div>
    <div>
        <input type="text" id="name"
        name="name" value="John Doe"
        size="40">
    </div>
</div>

4.4. Выражения включения фрагментов

Фрагменты Thymeleaf предлагают другие интересные опции, такие как поддержка условных выражений для определения необходимости включения фрагмента.

Используя оператор Elvis с любым из выражений, предоставляемых Thymeleaf (например, безопасность, строки и коллекции), мы можем загружать разные фрагменты.

Например, мы можем определить этот фрагмент с некоторым содержимым, которое мы будем показывать в зависимости от заданного условия. Это может быть файл, содержащий различные типы блоков:

<div th:fragment="dataPresent">Data received</div>
<div th:fragment="noData">No data</div>

И вот как мы можем загрузить их с помощью выражения:

<div
    th:replace="${#lists.size(data) > 0} ? 
        ~{fragments/menus.html :: dataPresent} : 
        ~{fragments/menus.html :: noData}">
</div>

«

«Чтобы узнать больше о Thymeleaf Expressions, ознакомьтесь с нашей статьей здесь.

4.5. Гибкие макеты

<table>
    <thead th:fragment="fields(theadFields)">
        <tr th:replace="${theadFields}">
        </tr>
    </thead>
    <tbody th:fragment="tableBody(tableData)">
        <tr th:each="row: ${tableData}">
            <td th:text="${row.id}">0</td>
            <td th:text="${row.name}">Name</td>
        </tr>
    </tbody>
    <tfoot>
    </tfoot>
</table>

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

<body>
    <header th:replace="fragments/general.html :: header"> </header>
    <table>
        <thead th:replace="fragments/tables.html
              :: fields(~{ :: .myFields})">
            <tr class="myFields">

                <th>Id</th>
                <th>Name</th>
            </tr>
        </thead>
        <div th:replace="fragments/tables.html
          :: tableBody(tableData=${data})">
        </div>
    </table>
    <div th:replace="fragments/general.html :: footer"></div>
</body>

Когда мы хотим использовать эту таблицу, мы можем передать наш собственный заголовок таблицы, используя поля функционируют. На заголовок ссылается класс myFields. Тело таблицы загружается путем передачи данных в качестве параметра функции tableBody:

<body>
    <div>
        <h1>Thymeleaf Fragments sample</h1>
    </div>
    <div>Data received</div>
    <table>
        <thead>
            <tr class="myFields">

                <th>Id</th>
                <th>Name</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>1001</td>
                <td>John Smith</td>
            </tr>
            <tr>
                <td>1002</td>
                <td>Jane Williams</td>
            </tr>
        </tbody>
    </table>
    <footer>
        <a href="/spring-thymeleaf/fragments">Fragments Index</a> |
        <a href="/spring-thymeleaf/markup">Markup inclussion</a> |
        <a href="/spring-thymeleaf/params">Fragment params</a> |
        <a href="/spring-thymeleaf/other">Other</a>
    </footer>
</body>

И вот как будет выглядеть окончательная страница:

5. Заключение

В этой статье мы Мы показали, как повторно использовать компоненты представления с помощью Thymeleaf Fragments, мощного инструмента, упрощающего управление шаблонами.

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

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