Contents

Handlebars 简介

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}}等标签组成。

然后我们通过传递上下文对象(如Map或其他Object)来填充这些标签。

3.1. 使用this

**要将单个String 值传递给我们的模板,我们可以使用任何Object作为上下文。**我们还必须在我们的模板中使用 {{this}} 标签。

然后 Handlebars 调用上下文对象的toString方法并用结果替换标签:

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

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

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

在上面的示例中,我们首先创建了一个Handlebars实例,这是我们的 API 入口点。

然后,我们给那个实例我们的模板。在这里,**我们只是内联传递模板,**但稍后我们会看到一些更强大的方法。

最后,我们为编译后的模板提供上下文。{{this}}将最终调用toString,这就是我们看到*“Hi Blogdemo!”*的原因。

3.2. 将Map作为上下文对象传递

我们刚刚看到了如何为我们的上下文发送一个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", "Blogdemo");
    
    String templateString = template.apply(parameterMap);
    
    assertThat(templateString).isEqualTo("Hi Blogdemo!");
}

与前面的示例类似,我们正在编译我们的模板,然后传递上下文对象,但这次是作为Map传递。

另外,请注意我们使用的是*{{name}}而不是{{this}}。**这意味着我们的Map必须包含键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("Blogdemo");
    
    String templateString = template.apply(person);
    
    assertThat(templateString).isEqualTo("Hi Blogdemo!");
}

我们模板中的*{{name}}将深入到我们的Person对象并获取name*字段的值。

4. 模板加载器

到目前为止,我们已经使用了在代码中定义的模板。但是,这不是唯一的选择。我们还可以从文本文件中读取模板。

Handlebars.java 为从类路径、文件系统或 servlet 上下文中读取模板提供特殊支持。默认情况下,Handlebars 扫描类路径以加载给定的模板:

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

**因此,因为我们调用了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在 classpath 中查找*/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 将在两个目录中搜索*greeting *模板。

5. 内置助手

内置助手在编写模板时为我们提供了额外的功能。

5.1. with帮手

with帮助程序更改当前上下文

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

在我们的示例模板中,*{{#with address}}标记开始该部分,{{/with}}*标记结束它。

本质上,我们正在深入了解当前的上下文对象——比方说人——并将address设置为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("Blogdemo");
    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

each迭代一个集合:

{{#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("Blogdemo");
    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提供条件渲染

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

在我们的模板中,我们根据*busy *字段提供不同的消息。

@Test
public void whenUsedIf_ThenPutsCondition() throws IOException {
    Handlebars handlebars = new Handlebars(templateLoader);
    Template template = handlebars.compile("if");
    Person person = getPerson("Blogdemo");
    person.setBusy(true);
    
    String templateString = template.apply(person);
    
    assertThat(templateString).contains("<h4>Blogdemo is busy.</h4>");
}

编译模板后,我们设置上下文对象。由于busy字段为true,最终输出变为<h4>Blogdemo is busy.</h4>*。*

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接口只有一个方法接受context options对象。出于我们的目的,我们将输出Person的*name busy *字段。

创建助手后,我们还必须向 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
}

在我们的示例中,我们使用Handlebars.registerHelper()方法以isBusy的名称注册我们的助手。

作为最后一步,我们必须使用助手的名称在我们的模板中定义一个标签

{{#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
}

由于一个 helper 源可以包含多个 helper 实现,注册不同于单个 helper 注册:

@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 *模板的内容——header.html

为了在另一个模板中使用它,我们必须引用*header *模板。

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

我们有page 模板 - page.html - 其中包括使用{{>header}} 的页眉模板。

Handlebars.java 处理模板时,最终的输出也会包含header的内容:

@Test
public void whenOtherTemplateIsReferenced_ThenCanReuse() throws IOException {
    Handlebars handlebars = new Handlebars(templateLoader);
    Template template = handlebars.compile("page");
    Person person = new Person();
    person.setName("Blogdemo");
    
    String templateString = template.apply(person);
    
    assertThat(templateString)
      .contains("<h4>Hi Blogdemo!</h4>", "<p>This is the page Blogdemo</p>");
}

7.2. 模板继承

作为组合的替代方案,Handlebars 提供了模板继承

我们可以使用*{{#block}}{{#partial}}*标签实现继承关系:

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

通过这样做,*messagebase *模板有两个块——*intro message *。

要应用继承,我们需要使用*{{#partial}}覆盖其他模板中的这些block *:

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

这是*simplemessage 模板。请注意,我们包含了messagebase 模板并覆盖了message *块。