Перейти к основному содержимому

Шаблоны с рулем

· 9 мин. чтения

1. Обзор

В этом руководстве мы рассмотрим библиотеку Handlebars.java для удобного управления шаблонами.

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

Начнем с добавления зависимости handlebars :

<dependency>
<groupId>com.github.jknack</groupId>
<artifactId>handlebars</artifactId>
<version>4.1.2</version>
</dependency>

3. Простой шаблон

Шаблон Handlebars может быть любым текстовым файлом. Он состоит из таких тегов, как {{name}} и {{#each people}}.

Затем мы заполняем эти теги, передавая объект контекста, например карту или другой объект.

3.1. Используя это

Чтобы передать одно строковое значение в наш шаблон, мы можем использовать любой объект в качестве контекста. Мы также должны использовать тег {{ this}} в нашем шаблоне.

Затем Handlebars вызывает метод toString для объекта контекста и заменяет тег результатом:

@Test
public void whenThereIsNoTemplateFile_ThenCompilesInline() throws IOException {
Handlebars handlebars = new Handlebars();
Template template = handlebars.compileInline("Hi {{this}}!");

String templateString = template.apply("ForEach");

assertThat(templateString).isEqualTo("Hi ForEach!");
}

В приведенном выше примере мы сначала создаем экземпляр Handlebars, нашу точку входа в API.

Затем мы даем этому экземпляру наш шаблон. Здесь мы просто передаем шаблон в строку, но вскоре мы увидим несколько более мощных способов.

Наконец, мы даем скомпилированному шаблону наш контекст. {{this}} просто вызовет toString, поэтому мы видим «Hi ForEach!» .

3.2. Передача карты как объекта контекста

Мы только что увидели, как отправить String для нашего контекста, теперь давайте попробуем Map :

@Test
public void whenParameterMapIsSupplied_thenDisplays() throws IOException {
Handlebars handlebars = new Handlebars();
Template template = handlebars.compileInline("Hi {{name}}!");
Map<String, String> parameterMap = new HashMap<>();
parameterMap.put("name", "ForEach");

String templateString = template.apply(parameterMap);

assertThat(templateString).isEqualTo("Hi ForEach!");
}

Как и в предыдущем примере, мы компилируем наш шаблон, а затем передаем объект контекста, но на этот раз как Map .

Также обратите внимание, что мы используем {{name}} вместо {{this}} . Это означает, что наша карта должна содержать ключ name .

3.3. Передача пользовательского объекта в качестве объекта контекста

Мы также можем передать пользовательский объект в наш шаблон:

public class Person {
private String name;
private boolean busy;
private Address address = new Address();
private List<Person> friends = new ArrayList<>();

public static class Address {
private String street;
}
}

Используя класс Person , мы добьемся того же результата, что и в предыдущем примере:

@Test
public void whenParameterObjectIsSupplied_ThenDisplays() throws IOException {
Handlebars handlebars = new Handlebars();
Template template = handlebars.compileInline("Hi {{name}}!");
Person person = new Person();
person.setName("ForEach");

String templateString = template.apply(person);

assertThat(templateString).isEqualTo("Hi ForEach!");
}

{{name}} в нашем шаблоне будет углубляться в наш объект Person и получать значение поля name .

4. Загрузчики шаблонов

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

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

@Test
public void whenNoLoaderIsGiven_ThenSearchesClasspath() throws IOException {
Handlebars handlebars = new Handlebars();
Template template = handlebars.compile("greeting");
Person person = getPerson("ForEach");

String templateString = template.apply(person);

assertThat(templateString).isEqualTo("Hi ForEach!");
}

Итак, поскольку мы вызвали compile вместо compileInline, это подсказка Handlebars искать /greeting.hbs в пути к классам.

Однако мы также можем настроить эти свойства с помощью ClassPathTemplateLoader :

@Test
public void whenClasspathTemplateLoaderIsGiven_ThenSearchesClasspathWithPrefixSuffix() throws IOException {
TemplateLoader loader = new ClassPathTemplateLoader("/handlebars", ".html");
Handlebars handlebars = new Handlebars(loader);
Template template = handlebars.compile("greeting");
// ... same as before
}

В данном случае мы указываем Handlebars искать /handlebars/greeting.html в пути к классам .

Наконец, мы можем связать несколько экземпляров TemplateLoader :

@Test
public void whenMultipleLoadersAreGiven_ThenSearchesSequentially() throws IOException {
TemplateLoader firstLoader = new ClassPathTemplateLoader("/handlebars", ".html");
TemplateLoader secondLoader = new ClassPathTemplateLoader("/templates", ".html");
Handlebars handlebars = new Handlebars().with(firstLoader, secondLoader);
// ... same as before
}

Итак, здесь у нас есть два загрузчика, и это означает, что Handlebars будет искать шаблон приветствия в двух каталогах.

5. Встроенные помощники

Встроенные помощники предоставляют нам дополнительную функциональность при написании наших шаблонов.

5.1. с помощником

Помощник with изменяет текущий контекст :

{{#with address}}
<h4>I live in {{street}}</h4>
{{/with}}

В нашем образце шаблона тег {{#with address}} открывает раздел, а тег {{/with}} завершает его .

По сути, мы углубляемся в текущий объект контекста — скажем, человека — и устанавливаем адрес в качестве локального контекста для секции with . После этого перед каждой ссылкой на поле в этом разделе будет стоять имя person.address .

Итак, тег {{street}} будет содержать значение person.address.street :

@Test
public void whenUsedWith_ThenContextChanges() throws IOException {
Handlebars handlebars = new Handlebars(templateLoader);
Template template = handlebars.compile("with");
Person person = getPerson("ForEach");
person.getAddress().setStreet("World");

String templateString = template.apply(person);

assertThat(templateString).contains("<h4>I live in World</h4>");
}

Мы компилируем наш шаблон и назначаем экземпляр Person в качестве объекта контекста. Обратите внимание, что класс Person имеет поле Address . Это поле, которое мы предоставляем помощнику with .

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

5.2. каждый помощник

Каждый помощник перебирает коллекцию :

{{#each friends}}
<span>{{name}} is my friend.</span>
{{/each}}

В результате запуска и закрытия раздела итерации с тегами {{#each friends}} и {{/each}} Handlebars будет выполнять итерацию по полю friends контекстного объекта.

@Test
public void whenUsedEach_ThenIterates() throws IOException {
Handlebars handlebars = new Handlebars(templateLoader);
Template template = handlebars.compile("each");
Person person = getPerson("ForEach");
Person friend1 = getPerson("Java");
Person friend2 = getPerson("Spring");
person.getFriends().add(friend1);
person.getFriends().add(friend2);

String templateString = template.apply(person);

assertThat(templateString)
.contains("<span>Java is my friend.</span>", "<span>Spring is my friend.</span>");
}

В этом примере мы назначаем два экземпляра Person в поле friends контекстного объекта. Таким образом, Handlebars дважды повторяет часть HTML в конечном выводе.

5.3. если помощник

Наконец, помощник if обеспечивает условный рендеринг .

{{#if busy}}
<h4>{{name}} is busy.</h4>
{{else}}
<h4>{{name}} is not busy.</h4>
{{/if}}

В нашем шаблоне мы предоставляем разные сообщения в зависимости от занятого поля.

@Test
public void whenUsedIf_ThenPutsCondition() throws IOException {
Handlebars handlebars = new Handlebars(templateLoader);
Template template = handlebars.compile("if");
Person person = getPerson("ForEach");
person.setBusy(true);

String templateString = template.apply(person);

assertThat(templateString).contains("<h4>ForEach is busy.</h4>");
}

После компиляции шаблона мы устанавливаем объект контекста. Так как поле занятости равно true , конечным выводом будет <h4>ForEach is busy.</h4> .

6. Пользовательские помощники по шаблонам

Мы также можем создавать собственные пользовательские помощники.

6.1. Помощник

Интерфейс Helper позволяет нам создать помощник шаблона.

В качестве первого шага мы должны предоставить реализацию Helper :

new Helper<Person>() {
@Override
public Object apply(Person context, Options options) throws IOException {
String busyString = context.isBusy() ? "busy" : "available";
return context.getName() + " - " + busyString;
}
}

Как мы видим, интерфейс Helper имеет только один метод, который принимает объекты контекста и опций . Для наших целей мы выведем поля имени и занятости Person .

После создания хелпера мы также должны зарегистрировать наш собственный хелпер с помощью Handlebars :

@Test
public void whenHelperIsCreated_ThenCanRegister() throws IOException {
Handlebars handlebars = new Handlebars(templateLoader);
handlebars.registerHelper("isBusy", new Helper<Person>() {
@Override
public Object apply(Person context, Options options) throws IOException {
String busyString = context.isBusy() ? "busy" : "available";
return context.getName() + " - " + busyString;
}
});

// implementation details
}

В нашем примере мы регистрируем наш помощник под именем isBusy, используя метод Handlebars.registerHelper() .

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

{{#isBusy this}}{{/isBusy}}

Обратите внимание, что у каждого помощника есть начальный и конечный теги.

6.2. Вспомогательные методы

Когда мы используем интерфейс Helper , мы можем создать только один helper . Напротив, вспомогательный исходный класс позволяет нам определить несколько помощников шаблона.

Более того, нам не нужно реализовывать какой-либо конкретный интерфейс. Мы просто пишем вспомогательные методы в классе, затем HandleBars извлекает определения вспомогательных функций с помощью отражения:

public class HelperSource {

public String isBusy(Person context) {
String busyString = context.isBusy() ? "busy" : "available";
return context.getName() + " - " + busyString;
}

// Other helper methods
}

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

@Test
public void whenHelperSourceIsCreated_ThenCanRegister() throws IOException {
Handlebars handlebars = new Handlebars(templateLoader);
handlebars.registerHelpers(new HelperSource());

// Implementation details
}

Мы регистрируем наши помощники, используя метод Handlebars.registerHelpers() . Более того, имя вспомогательного метода становится именем вспомогательного тега .

7. Повторное использование шаблона

Библиотека Handlebars предоставляет несколько способов повторного использования наших существующих шаблонов.

7.1. Включение шаблона

Включение шаблона — один из подходов к повторному использованию шаблонов. Это способствует композиции шаблонов .

<h4>Hi {{name}}!</h4>

Это содержимое шаблона заголовка — header.html.

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

{{>header}}
<p>This is the page {{name}}</p>

У нас есть шаблон страницы — page.html — который включает шаблон заголовка с использованием {{>header}}.

Когда Handlebars.java обрабатывает шаблон, окончательный вывод также будет содержать содержимое заголовка :

@Test
public void whenOtherTemplateIsReferenced_ThenCanReuse() throws IOException {
Handlebars handlebars = new Handlebars(templateLoader);
Template template = handlebars.compile("page");
Person person = new Person();
person.setName("ForEach");

String templateString = template.apply(person);

assertThat(templateString)
.contains("<h4>Hi ForEach!</h4>", "<p>This is the page ForEach</p>");
}

7.2. Наследование шаблонов

В качестве альтернативы композиции Handlebars обеспечивает наследование шаблонов .

Мы можем достичь отношений наследования, используя теги {{#block}} и {{#partial}} :

<html>
<body>
{{#block "intro"}}
This is the intro
{{/block}}
{{#block "message"}}
{{/block}}
</body>
</html>

Таким образом, шаблон базы сообщений состоит из двух блоков — вступления и сообщения .

Чтобы применить наследование, нам нужно переопределить эти блоки в других шаблонах, используя {{#partial}} :

{{#partial "message" }}
Hi there!
{{/partial}}
{{> messagebase}}

Это шаблон простого сообщения . Обратите внимание, что мы включаем шаблон базы сообщений, а также переопределяем блок сообщений .

8. Резюме

В этом руководстве мы рассмотрели Handlebars.java для создания шаблонов и управления ими.

Мы начали с базового использования тегов, а затем рассмотрели различные варианты загрузки шаблонов Handlebars.

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

Наконец, ознакомьтесь с исходным кодом всех примеров на GitHub .