«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("Baeldung");
    
    assertThat(templateString).isEqualTo("Hi Baeldung!");
}

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

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

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

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

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

@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", "Baeldung");
    
    String templateString = template.apply(parameterMap);
    
    assertThat(templateString).isEqualTo("Hi Baeldung!");
}

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

Также обратите внимание, что мы используем {{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("Baeldung");
    
    String templateString = template.apply(person);
    
    assertThat(templateString).isEqualTo("Hi Baeldung!");
}

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

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

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

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

@Test
public void whenNoLoaderIsGiven_ThenSearchesClasspath() throws IOException {
    Handlebars handlebars = new Handlebars();
    Template template = handlebars.compile("greeting");
    Person person = getPerson("Baeldung");
    
    String templateString = template.apply(person);
    
    assertThat(templateString).isEqualTo("Hi Baeldung!");
}

Итак, поскольку мы вызвали 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 Helper

Помощник 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("Baeldung");
    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 Helper

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

{{#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("Baeldung");
    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("Baeldung");
    person.setBusy(true);
    
    String templateString = template.apply(person);
    
    assertThat(templateString).contains("<h4>Baeldung is busy.</h4>");
}

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

6. Пользовательские хелперы шаблонов

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

6.1. Helper

Интерфейс 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. Вспомогательные методы

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

Более того, нам не нужно реализовывать какой-то конкретный интерфейс. Мы просто пишем наши вспомогательные методы в классе, затем 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 – который включает шаблон заголовка с использованием {{\u003eheader}}.

Когда 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("Baeldung");
    
    String templateString = template.apply(person);
    
    assertThat(templateString)
      .contains("<h4>Hi Baeldung!</h4>", "<p>This is the page Baeldung</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.