Java: Веб-технологии

Теория: Лейауты в JTE

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

Инвертированный подход к повторяющимся блокам

Представьте себе типовой сайт. На большинстве сайтов есть верхнее меню, футер и еще немало повторяющихся блоков. Как с ними правильно работать? Кажется, что копировать все это в каждый шаблон — не самая лучшая идея.

Есть еще один подход — вынести общие куски в отдельные файлы и включить их в нужных шаблонах:

<!-- Пример шаблонизатора JSP -->
<%@ include file="parts/header.jsp" %>
<!-- Здесь код конкретного шаблона -->
<%@ include file="parts/footer.jsp" %>

Это простое решение, которые использовалось в самых ранних шаблонизаторах. Разработчики быстро поняли, насколько это неудобно. Проблема заключается в двух аспектах:

  • Разработчику приходится делать подобные включения абсолютно в каждом шаблоне вручную. В нашем примере их всего два. А что, если бы их были сотни? На работу с каждым отдельным шаблоном ушло бы очень много времени
  • Разработчику приходится вручную проверять разные файлы и искать незакрытые теги, потому что части одних и тех же тегов могут быть разбросаны по разным файлам. Отлаживать такие проекты очень сложно

Эти проблемы решил инвертированный подход, в котором не куски сайта вставляются в шаблон, а шаблон вставляется в макет сайта. Принцип здесь такой:

  • В одном месте описываем макет – основу сайта, которая не меняется по структуре
  • Шаблонизатор делает так, чтобы шаблон конкретной страницы вставлялся в этот макет

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

Инвертированный подход в JTE

Шаблонизатор JTE поддерживает работу с макетами. Посмотрим, как это работает. Для начала создадим макет src/main/jte/layout/page.jte:

@import gg.jte.Content
@param Content content

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>Hexlet Javalin Example</title>
    </head>
    <body>
        <p>
            <a href="/users">Users</a>
            <a href="/courses">Courses</a>
        </p>
        ${content}
    </body>
</html>

Обратим внимание на ${content} — сюда JTE поместит шаблон конкретной страницы. Посмотрим, как выглядит шаблон списка пользователей:

@import org.example.hexlet.dto.UsersPage
@param UsersPage page

@template.layout.page(
    content = @`
        <a href="/users/build">New User</a>
        @for(var user : page.getUsers())
            <div>${user.getName()}</div>
        @endfor
    `
)

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

@template.layout.page(content = @``)

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

@import org.example.Page
@import gg.jte.Content

@param Page page
@param Content content
@param Content footer = null

<head>
    @if(page.getDescription() != null)
        <meta name="description" content="${page.getDescription()}">
    @endif
    <title>${page.getTitle()}</title>
</head>
<body>
    <h1>${page.getTitle()}</h1>
    <div class="content">
        ${content}
    </div>
    @if (footer != null)
        <div class="footer">
            ${footer}
        </div>
    @endif
</body>

Кроме content, в этом макете используется footer и page, которые придут из конкретного шаблона. По умолчанию footer равен null, поэтому мы можем его и не передавать. Соответствующий блок будет скрыт:

@import org.example.WelcomePage
@param WelcomePage welcomePage

@template.layout.page(
    page = welcomePage,
    content = @`
        <p>Welcome, ${welcomePage.getUserName()}.</p>
    `,
    footer = @`
        <p>Thanks for visiting, come again soon!</p>
    `
)

Рекомендуемые программы

Завершено

0 / 23