FreeMarker

Apache FreeMarker 是一个模板引擎,用于生成从 HTML 到电子邮件等各种文本输出。 Infra Framework 内置了使用 FreeMarker 模板的 Infra MVC 集成。

视图配置

以下示例展示了如何将 FreeMarker 配置为视图技术:

  • Java

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

  @Override
  public void configureViewResolvers(ViewResolverRegistry registry) {
    registry.freeMarker();
  }

  // 配置 FreeMarker...

  @Bean
  public FreeMarkerConfigurer freeMarkerConfigurer() {
    FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
    configurer.setTemplateLoaderPath("classpath:freemarker");
    return configurer;
  }
}

或者,您也可以声明 FreeMarkerConfigurer bean 以完全控制所有属性,如下例所示:

<bean id="freemarkerConfig" class="infra.web.view.freemarker.FreeMarkerConfigurer">
  <property name="templateLoaderPath" value="classpath:freemarker/"/>
</bean>

您的模板需要存储在前面示例中显示的 FreeMarkerConfigurer 指定的目录中。 鉴于前面的配置,如果您的控制器返回 welcome 视图名称,解析器将查找 classpath:freemarker/welcome.ftl 模板。

FreeMarker 配置

您可以通过在 FreeMarkerConfigurer bean 上设置相应的 bean 属性,将 FreeMarker 'Settings' 和 'SharedVariables' 直接传递给 FreeMarker Configuration 对象(由 Infra 管理)。 freemarkerSettings 属性需要一个 java.util.Properties 对象,而 freemarkerVariables 属性需要一个 java.util.Map。 以下示例展示了如何使用 FreeMarkerConfigurer

<bean id="freemarkerConfig" class="infra.web.view.freemarker.FreeMarkerConfigurer">
  <property name="templateLoaderPath" value="classpath:freemarker/"/>
  <property name="freemarkerVariables">
    <map>
      <entry key="xml_escape" value-ref="fmXmlEscape"/>
    </map>
  </property>
</bean>

<bean id="fmXmlEscape" class="freemarker.template.utility.XmlEscape"/>

有关应用于 Configuration 对象的设置和变量的详细信息,请参阅 FreeMarker 文档。

表单处理

Infra 提供了一个用于 FreeMarker 显示来自 Web 或业务层中 Validator 的失败验证结果。

绑定宏

一组标准的宏在 infra-webmvc.jar 文件中为 FreeMarker 维护,因此它们始终可用于配置适当的应用程序。

Infra 模板库中定义的一些宏被认为是内部的(私有的),但宏定义中不存在此类作用域,使得所有宏对调用代码和用户模板可见。 以下部分仅关注您需要从模板中直接调用的宏。 如果您想直接查看宏代码,该文件名为 infra.ftl,位于 根目录 包中。

简单绑定

在基于充当 Infra MVC 控制器表单视图的 FreeMarker 模板的 HTML 表单中,您可以使用类似于下一个示例的代码绑定到字段值, 并以等效项类似的方式显示每个输入字段的错误消息。 以下示例显示了 personForm 视图:

<!-- FreeMarker 宏必须导入到命名空间中。
	我们强烈建议坚持使用 'infra'。 -->
<#import "/infra.ftl" as infra/>
<html>
	...
	<form action="" method="POST">
		Name:
		<@infra.bind "personForm.name"/>
		<input type="text"
			name="${infra.status.expression}"
			value="${infra.status.value?html}"/><br />
		<#list infra.status.errorMessages as error> <b>${error}</b> <br /> </#list>
		<br />
		...
		<input type="submit" value="submit"/>
	</form>
	...
</html>

名为 <@infra.bindEscaped> 的宏的另一种形式采用第二个参数,该参数显式指定是否应在状态错误消息或值中使用 HTML 转义。 您可以根据需要将其设置为 truefalse。 其他表单处理宏简化了 HTML 转义的使用,您应该尽可能使用这些宏。 下一节将对它们进行解释。

输入宏

FreeMarker 的其他便捷宏简化了绑定和表单生成(包括验证错误显示)。 从来没有必要使用这些宏来生成表单输入字段,您可以将它们与简单的 HTML 或直接调用我们之前重点介绍的 Infra 绑定宏混合使用。

下表列出了可用的宏,显示了 FreeMarker 模板 (FTL) 定义以及每个宏采用的参数列表:

Table 1. 宏定义表
FTL 定义

message (根据代码参数从资源包输出字符串)

<@infra.message code/>

messageText (根据代码参数从资源包输出字符串,回退到默认参数的值)

<@infra.messageText code, text/>

formInput (用于收集用户输入的标准输入字段)

<@infra.formInput path, attributes, fieldType/>

formHiddenInput (用于提交非用户输入的隐藏输入字段)

<@infra.formHiddenInput path, attributes/>

formPasswordInput (用于收集密码的标准输入字段。请注意,此类型的字段中永远不会填充任何值。)

<@infra.formPasswordInput path, attributes/>

formTextarea (用于收集长格式自由文本输入的大文本字段)

<@infra.formTextarea path, attributes/>

formSingleSelect (允许选择单个所需值的选项下拉框)

<@infra.formSingleSelect path, options, attributes/>

formMultiSelect (允许用户选择 0 个或多个值的选项列表框)

<@infra.formMultiSelect path, options, attributes/>

formRadioButtons (一组单选按钮,允许从可用选项中进行单选)

<@infra.formRadioButtons path, options separator, attributes/>

formCheckboxes (一组复选框,允许选择 0 个或多个值)

<@infra.formCheckboxes path, options, separator, attributes/>

formCheckbox (单个复选框)

<@infra.formCheckbox path, attributes/>

showErrors (简化绑定字段的验证错误显示)

<@infra.showErrors separator, classOrStyle/>

在 FreeMarker 模板中,formHiddenInputformPasswordInput 实际上不是必需的, 因为您可以使用普通的 formInput 宏,将 hiddenpassword 指定为 fieldType 参数的值。

上述任何宏的参数都具有一致的含义:

  • path: 要绑定到的字段的名称(例如 "command.name")

  • options: 所有可用值的 Map,可以在输入字段中从中进行选择。 映射的键表示从表单 POST 回并绑定到命令对象的值。 针对键存储的映射对象是显示在表单上给用户的标签,可能与表单发回的相应值不同。 通常,此类映射由控制器作为参考数据提供。 您可以根据所需的行为使用任何 Map 实现。 对于严格排序的映射,您可以使用带有合适 ComparatorSortedMap(例如 TreeMap), 对于应按插入顺序返回值的任意映射,请使用 LinkedHashMap 或来自 commons-collectionsLinkedMap

  • separator: 当多个选项作为离散元素(单选按钮或复选框)可用时,用于分隔列表中每个选项的字符序列(例如 <br>)。

  • attributes: 要包含在 HTML 标签本身的任意标签或文本的附加字符串。 此字符串由宏按字面意思回显。 例如,在 textarea 字段中,您可以提供属性(例如 'rows="5" cols="60"'),或者您可以传递样式信息,例如 'style="border:1px solid silver"'。

  • classOrStyle: 对于 showErrors 宏,包装每个错误的 span 元素使用的 CSS 类的名称。 如果没有提供信息(或值为空),则错误将包装在 <b></b> 标签中。

以下部分概述了宏的示例。

输入字段

formInput 宏采用 path 参数 (command.name) 和一个附加的 attributes 参数(在接下来的示例中为空)。 该宏与所有其他表单生成宏一样,对路径参数执行隐式 Infra 绑定。 绑定在发生新绑定之前一直有效,因此 showErrors 宏不需要再次传递路径参数——它对上次创建绑定的字段进行操作。

showErrors 宏采用分隔符参数(用于分隔给定字段上的多个错误的字符),并且还接受第二个参数——这次是类名或样式属性。 请注意,FreeMarker 可以为属性参数指定默认值。 以下示例展示了如何使用 formInputshowErrors 宏:

<@infra.formInput "command.name"/>
<@infra.showErrors "<br>"/>

下一个示例显示了表单片段的输出,生成名称字段并在表单提交且字段中没有值后显示验证错误。 验证通过 Infra 的验证框架进行。

生成的 HTML 类似于以下示例:

Name:
<input type="text" name="name" value="">
<br>
	<b>required</b>
<br>
<br>

formTextarea 宏的工作方式与 formInput 宏相同,并且接受相同的参数列表。 通常,第二个参数 (attributes) 用于传递样式信息或 textarearowscols 属性。

选择字段

您可以使用四个选择字段宏在 HTML 表单中生成常见的 UI 值选择输入:

  • formSingleSelect

  • formMultiSelect

  • formRadioButtons

  • formCheckboxes

四个宏中的每一个都接受一个选项 Map,其中包含表单字段的值以及与该值对应的标签。 值和标签可以相同。

下一个示例是 FTL 中的单选按钮。表单支持对象为此字段指定默认值 'London',因此无需验证。 当渲染表单时,可供选择的整个城市列表作为参考数据在名称 'cityMap' 下的模型中提供。 以下清单显示了该示例:

...
Town:
<@infra.formRadioButtons "command.address.town", cityMap, ""/><br><br>

前面的清单呈现了一行单选按钮,cityMap 中的每个值对应一个,并使用 "" 分隔符。 未提供其他属性(缺少宏的最后一个参数)。 cityMap 对映射中的每个键值对使用相同的 String。 映射的键是表单实际作为 POST 请求参数提交的内容。映射值是用户看到的标签。 在前面的示例中,给定三个著名城市的列表和表单支持对象中的默认值,HTML 类似于以下内容:

Town:
<input type="radio" name="address.town" value="London">London</input>
<input type="radio" name="address.town" value="Paris" checked="checked">Paris</input>
<input type="radio" name="address.town" value="New York">New York</input>

如果您的应用程序希望通过内部代码(例如)处理城市,您可以创建具有合适键的代码映射,如下例所示:

  • Java

protected Map<String, ?> referenceData(RequestContext request) throws Exception {
	Map<String, String> cityMap = new LinkedHashMap<>();
	cityMap.put("LDN", "London");
	cityMap.put("PRS", "Paris");
	cityMap.put("NYC", "New York");

	Map<String, Object> model = new HashMap<>();
	model.put("cityMap", cityMap);
	return model;
}

代码现在生成的输出中,单选值是相关的代码,但用户仍然看到更友好的城市名称,如下所示:

Town:
<input type="radio" name="address.town" value="LDN">London</input>
<input type="radio" name="address.town" value="PRS" checked="checked">Paris</input>
<input type="radio" name="address.town" value="NYC">New York</input>

HTML 转义

要为您的标记切换到 XHTML 合规性,请为名为 xhtmlCompliant 的模型或上下文变量指定值 true,如下例所示:

<#-- for FreeMarker -->
<#assign xhtmlCompliant = true>

处理此指令后,Infra 宏生成的任何元素现在都符合 XHTML。

以类似的方式,您可以为每个字段指定 HTML 转义,如下例所示:

<#-- until this point, default HTML escaping is used -->

<#assign htmlEscape = true>
<#-- next field will use HTML escaping -->
<@infra.formInput "command.name"/>

<#assign htmlEscape = false in infra>
<#-- all future fields will be bound with HTML escaping off -->