;
现在给form添加一个动态table
1 <table>
2 <thead>
3 <tr>
4 <th th:text="#{seedstarter.rows.head.rownum}">Row</th>
5 <th th:text="#{seedstarter.rows.head.variety}">Variety</th>
6 <th th:text="#{seedstarter.rows.head.seedsPerCell}">Seeds per cell</th>
7 <th>
8 <button type="submit" name="addRow" th:text="#{seedstarter.row.add}">Add row</button>
9 </th>
10 </tr>
11 </thead>
12 <tbody>
13 <tr th:each="row,rowStat : *{rows}">
14 <td th:text="${rowStat.count}">1</td>
15 <td>
16 <select th:field="*{rows[__${rowStat.index}__].variety}">
17 <option th:each="var : ${allVarieties}"
18 th:value="${var.id}"
19 th:text="${var.name}">Thymus Thymi</option>
20 </select>
21 </td>
22 <td>
23 <input type="text" th:field="*{rows[__${rowStat.index}__].seedsPerCell}" />
24 </td>
25 <td>
26 <button type="submit" name="removeRow"
27 th:value="${rowStat.index}" th:text="#{seedstarter.row.remove}">Remove row</button>
28 </td>
29 </tr>
30 </tbody>
31 </table>
这里出现了很多东西,但都不难理解,除了这一句:
1 <select th:field="*{rows[__${rowStat.index}__].variety}">
2 ...
3 </select>
如果你记得Thymeleaf教程,那么应该明白 __${...}__ 是一种预处理表达式的语法。这是一个在处理整个表达式之前的内部表达式,但为什么用这种方式指定行的索引呢,下面这种方式不行么:
1 <select th:field="*{rows[rowStat.index].variety}">
2 ...
3 </select>
嗯事实上,是不行的,他的问题是SpringEL表达式不执行数值中括号里边的表达式变量,索引执行上边的语句时,会得到一个错误的结果,就是字面形式的 row[rowStat.index] (而不是 row[0],row[1] )而不是行集合中的正确位置,这就是为什么在这里需要预处理。
让我们看看产生的html后按"添加行"按钮几次:
1 <tbody>
2 <tr>
3 <td>1</td>
4 <td>
5 <select id="rows0.variety" name="rows[0].variety">
6 <option selected="selected" value="1">Thymus vulgaris</option>
7 <option value="2">Thymus x citriodorus</option>
8 <option value="3">Thymus herba-barona</option>
9 <option value="4">Thymus pseudolaginosus</option>
10 <option value="5">Thymus serpyllum</option>
11 </select>
12 </td>
13 <td>
14 <input id="rows0.seedsPerCell" name="rows[0].seedsPerCell" type="text" value="" />
15 </td>
16 <td>
17 <button name="removeRow" type="submit" value="0">Remove row</button>
18 </td>
19 </tr>
20 <tr>
21 <td>2</td>
22 <td>
23 <select id="rows1.variety" name="rows[1].variety">
24 <option selected="selected" value="1">Thymus vulgaris</option>
25 <option value="2">Thymus x citriodorus</option>
26 <option value="3">Thymus herba-barona</option>
27 <option value="4">Thymus pseudolaginosus</option>
28 <option value="5">Thymus serpyllum</option>
29 </select>
30 </td>
31 <td>
32 <input id="rows1.seedsPerCell" name="rows[1].seedsPerCell" type="text" value="" />
33 </td>
34 <td>
35 <button name="removeRow" type="submit" value="1">Remove row</button>
36 </td>
37 </tr>
38 </tbody>
验证和错误信息
让我们看看当有错误的时候如何给一个表单域一个CSS类:
1 <input type="text" th:field="*{datePlanted}"
2 th:class="${#fields.hasErrors('datePlanted')}? fieldError" />
可以看到, #fields.hasErrors(...) 函数接受一个表达式参数(datePlanted),返回一个布尔值告诉field该字段是否有验证错误。
我们可以根据他们各自的field获取所有的错误:
1 <ul>
2 <li th:each="err : ${#fields.errors('datePlanted')}" th:text="${err}" />
3 </ul>
通过迭代,我们可以使用 th:errors ,一个专门用于创建一个通过制定选择器筛选的错误列表的属性,通过 分隔。
1 <input type="text" th:field="*{datePlanted}" />
2 <p th:if="${#fields.hasErrors('datePlanted')}" th:errors="*{datePlanted}">Incorrect date</p>
简单错误基础css样式,th:errorclass
在上边的例子中,如果字段有错误,将为表单的input域设置一个css类,因为这种方式很常见,Thymeleaf提供了一个特定的属性为 th:errorclass
应用于form域的标签(input,select,textarea等),它将从现有的name属性或th:field属性字段的名词相同的属性,如果发生错误,则将制定的css类追加到标签中。
<input type="text" th:field="*{datePlanted}" class="small" th:errorclass="fieldError" />
如果datePlanted发生错误,则:
<input type="text" id="datePlanted" name="datePlanted" value="2013-01-01" class="small fieldError" />
全部错误
如果我们想要在form中显示所有的错误呢?我们只需要通过'*'或'all'(等价)来查询 #field.hasErrors(...) 方法和 #field.errors(...) 方法:
1 <ul th:if="${#fields.hasErrors('*')}">
2 <li th:each="err : ${#fields.errors('*')}" th:text="${err}">Input is incorrect</li>
3 </ul>
在上边的例子中,我们得到所有的错误并迭代他们:
1 <ul>
2 <li th:each="err : ${#fields.errors('*')}" th:text="${err}" />
3 </ul>
建立一个以<Enter>分隔的列表:
1 <p th:if="${#fields.hasErrors('all')}" th:errors="*{all}">Incorrect date</p>
最后,注意 #field.hasErrors("") 等效的属性 #fields.hasAnyErrors() 和 #fields.errors() 的等效的 #fields.allErrors() ,可以使用喜欢的任何语法。
1 <div th:if="${#fields.hasAnyErrors()}">
2 <p th:each="err : ${#fields.allErrors()}" th:text="${err}">...</p>
3 </div>
全局错误
Spring表单还有一种错误,全局错误,都是些不与窗体的任何特定字段关联的错误。
Thymeleaf提供了一个global的常量来访问这些错误。
1 <ul th:if="${#fields.hasErrors('global')}">
2 <li th:each="err : ${#fields.errors('global')}" th:text="${err}">Input is incorrect</li>
3 </ul>
Incorrect date以及等效的 #field.hasGlobalErrors() 和 #field.globalErrors() 方法。
1 <div th:if="${#fields.hasGlobalErrors()}">
2 <p th:each="err : ${#fields.globalErrors()}" th:text="${err}">...</p>
3 </div>
在表单外部显示错误
表单验证错误也可以在表单外部显示,方法是通过变量(即${...})的内部选择变量(*{...})增加表单bean的名字作为前缀的方式。
1 <form>
2 <div th:errors="${myForm}">...</div>
3 <div th:errors="${myForm.date}">...</div>
4 <div th:errors="${myForm.*}">...</div>
5 <div th:if="${#fields.hasErrors('${myForm}')}">...</div>
6 <div th:if="${#fields.hasErrors('${myForm.date}')}">...</div>
7 <div th:if="${#fields.hasErrors('${myForm.*}')}">...</div>
8 <form th:object="${myForm}">
9 ...
10 </form>
富错误对象
Thymeleaf提供了以bean的形式(代替单纯的String)提供错误信息的能力,包括fieldName(String),message(String),和global(String)属性的错误。这些错误可以通过工具方法#fields.datailedErrors()来实现:
1 <ul>
2 <li th:each="e : ${#fields.detailedErrors()}" th:class="${e.global}? globalerr : fielderr">
3 <span th:text="${e.global}? '*' : ${e.fieldName}">The field name</span> |
4 <span th:text="${e.message}">The error message</span>
5 </li>
6 </ul>
转换服务
配置
就像前文所说,Thymeleaf可以在上下文中注册一个转换服务,再次看一下他的配置信息
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans ...>
3 ...
4 <mvc:annotation-driven conversion-service="conversionService" />
5 ...
6 <!-- **************************************************************** -->
7 <!-- CONVERSION SERVICE -->
8 <!-- Standard Spring formatting-enabled implementation -->
9 <!-- **************************************************************** -->
10 <bean id="conversionService"
11 class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
12 <property name="formatters">
13 <set>
14 <bean class="thymeleafexamples.stsm.web.conversion.VarietyFormatter" />
15 <bean class="thymeleafexamples.stsm.web.conversion.DateFormatter" />
16 </set>
17 </property>
18 </bean>
19 ...
20 </beans>
${{...}}语法
转换服务可以通过 ${{...}} 语法很轻松的实现对象到字符串的转换或格式化:
变量语法 ${{...}}
选择变量语法 *{{...}}
例如,将一个Integer型转换为字符串类型,并通过逗号来分隔:
1 <p th:text="${val}">...</p>
2 <p th:text="${{val}}">...</p>
返回结果为:
1 <p>1234567890</p>
2 <p>1,234,567,890</p>
表单中使用
我们之前看到的每一个th:field属性都将始终使用转换服务:
<input type="text" th:field="*{datePlanted}" />
<input type="text" th:field="*{{datePlanted}}" />
注意这是唯一一种在表达式中使用单大括号的转换服务。
#conversions工具对象
conversions工具对象表达式允许手动执行转换服务:
<p th:text="${'Val: ' + #conversions.convert(val,'String')}">...</p>
工具对象表达式的语法为:
conversions.convert(Object,Class) :将对象转换为指定的类
conversions.convert(Object,String) :和上边相同,但是指定的目标为String类(java.lang包名可以省略)
渲染片段模板
Thymeleaf提供了将一个模板只渲染一部分,并作为一个片段返回的能力。
这是一个非常有用的组件化工具,比如,它可以用于执行AJAX的Controller的调用,用于在已经加载的浏览器中返回一个片段标签(如用于更新选择,启用禁用按钮等)。
片段渲染可以使用Thymeleaf的片段规范:一个实现了 org.thymeleaf.fragment.IFragmentSpec 接口的对象。
最常用的一个实现是 org.thymeleaf.standard.fragment.StandardDOMSelectorFragmentSpec 类,它允许一个片段规范包括之前说过的th:insert,th:replace使用DOM选择器。
在视图bean中指定片段
视图bean是在应用程序上下文中声明的 org.thymeleaf.spring4.view.ThymeleafView 的bean,它允许这样定义一个片段:
1 <bean name="content-part" class="org.thymeleaf.spring4.view.ThymeleafView">
2 <property name="templateName" value="index" />
3 <property name="fragmentSpec">
4 <bean
5 class="org.thymeleaf.standard.fragment.StandardDOMSelectorFragmentSpec"
6 c:selectorExpression="content" />
7 </property>
8 </bean>
通过上边的bean的定义,如果controller返回一个content-part(bean的名字),
1 @RequestMapping("/showContentPart")
2 public String showContentPart() {
3 ...
4 return "content-part";
Thymeleaf将只返回index模板的content片段。一旦前缀后缀都设置并匹配,那么它可能为/WEB-INF/templates/index.html,
1 <!DOCTYPE html>
2 <html>
3 ...
4 <body>
5 ...
6 <div th:fragment="content">
7 只有这里渲染!!
8 </div>
9 ...
10 </body>
11 </html>
另外应该注意到,因为Thymeleaf可以使用DOM选择器,所有我们可以不用任何 th:fragment 属性,而只用id属性来选择一个片段,如:
1 <bean name="content-part" class="org.thymeleaf.spring4.view.ThymeleafView">
2 <property name="fragmentSpec">
3 <bean class="org.thymeleaf.standard.fragment.StandardDOMSelectorFragmentSpec"
4 c:selectorExpression="#content" />
5 </property>
6 <property name="templateName" value="index" />
7 </bean>
同样完美的适用:
1 <!DOCTYPE html>
2 <html>
3 ...
4 <body>
5 ...
6 <div id="content">
7 只有这里渲染!!
8 </div>
9 ...
10 </body>
11 </html>
通过控制权的返回值指定片段
不声明一个视图bean,可以从控制器自己就可以使用与片段相同的语法,类似于th:insert,th:rplace属性等,如:
1 @RequestMapping("/showContentPart")
2 public String showContentPart() {
3 ...
4 return "index :: content";
当然,同样可以使用基于DOM选择器的功能,所有我们也可以是选择使用基于标准的HTML属性,如id="content"
1 @RequestMapping("/showContentPart")
2 public String showContentPart() {
3 ...
4 return "index :: #content";
也可以使用参数:
1 @RequestMapping("/showContentPart")
2 public String showContentPart() {
3 ...
4 return "index :: #content ('myvalue')";
与RequestDataValueProcessor集成
现在Thymeleaf无缝的与Spring的RequestDataValueProcessor接口集成,这个接口允许拦截链接URLS,表达URLS和表达域的值,以及为了启用安全,如抵御CSRF而自动透明的添加一些隐藏域。
在应用的上下文中可以简单的配置RequestDataValueProcessor:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans
5 http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
7 ...
9 <bean name="requestDataValueProcessor"
10 class="net.example.requestdata.processor.MyRequestDataValueProcessor" />
12 </beans>
Thymeleaf将通过这种方式使用它:
在渲染URL之前,th:href和th:src将会调用 RequestDataValueProcessor.processUrl(...)
在渲染表单的action属性之前,th:action会调用 RequestDataValueProcessor.processAction(...) ,另外他会检查标签,因为一般来说这是使用action的唯一一个地方,并且在的关闭标签之前执行 RequstDataValueProcessor.getExtraHiddenFields(...) 用来新增返回的hidden域。
在渲染value属性之前,th:value会调用 RequestDataProcessor.processFormFieldValue(...) ,除非在这个标签中存在了th:field(这时候th:field属性起作用)
当存在th:field的时候,在渲染value属性之前会调用 RequestDataValueProcessor.processFormFieldValue(...) 处理这个属性值(<textarea>处理内容值)
此功能只有Spring3.x以后使用
绑定地址到Controller
在Spring4.1之后的版本中,Spring允许通过注解直接从从视图链接到控制器,而不需要知道这些控制器映射的URI.
在Thymeleaf中可以通过 #mvc.url(...) 表达式方法调用Controller中符合驼峰命名规则的方法(get,set),调用的方式为方法的名字,即相当于jsp的spring:mvcUrl(...)自定义方法。
1 public class ExampleController {
2 @RequestMapping("/data")
3 public String getData(Model model) { ... return "template" }
4 @RequestMapping("/data")
5 public String getDataParam(@RequestParam String type) {
6 ...
7 return "template" }
下边是一个链接到它的方法:
1 <a th:href="${(#mvc.url('EC#getData')).build()}">获取Data参数</a>
2 <a th:href="${(#mvc.url('EC#getDataParam').arg(0,'internal')).build()}">获取Data参数</a>
Spring WebFlow的集成
基础配置
Thymeleaf-spring4集成包包括与Spring WebFlow 2.3.x的集成
WebFlow包括当特定的事件(过渡)被触发时渲染页面片段的一些Ajax的功能,未来让Thymeleaf参加这些Ajax请求,我们将使用一个不通过的视图解析器的实现,它这样配置:
1 <bean id="thymeleafViewResolver" class="org.thymeleaf.spring4.view.AjaxThymeleafViewResolver">
2 <property name="viewClass" value="org.thymeleaf.spring4.view.FlowAjaxThymeleafView" />
3 <property name="templateEngine" ref="templateEngine" />
4 </bean>
然后在ViewResolver中配置WebFlow的 ViewFactoryCreator .
1 <bean id="mvcViewFactoryCreator"
2 class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
3 <property name="viewResolvers" ref="thymeleafViewResolver"/>
4 </bean>
在这里可以指定模板的视图状态
1 <view-state id="detail" view="bookingDetail">
2 ...
3 </view-state>
在上边的实例中,bookingDetail是Thymeleaf模板通常使用的一个方式,是模板引擎内任何模板解析器都可以懂的
Ajax片段
WebFlow的片段规范允许片段通过标签呈现,就像这样:
1 <view-state id="detail" view="bookingDetail">
2 <transition on="updateData">
3 <render fragments="hoteldata"/>
4 </transition>
5 </view-state>
这些片段(即hoteldata)可以是逗号分隔的列表标记在th:fragment标签中。
1 <div id="data" th:fragment="hoteldata">
这里内容替换</div>
永远记住,指定的片段必须有一个id属性,这样浏览器运行的SpringJavaScript库才能对标签进行替换。
标签,也可以通过DOM选择器设定:
1 <view-state id="detail" view="bookingDetail">
2 <transition on="updateData">
3 <render fragments="[//div[@id='data']]"/>
4 </transition>
5 </view-state>
这将意味着th:fragment不在需要:
1 <div id="data">
2 This is a content to be changed
3 </div>
而出发updateData后转换的代码:
1 <script type="text/javascript" th:src="@{/resources/dojo/dojo.js}"></script>
2 <script type="text/javascript" th:src="@{/resources/spring/Spring.js}"></script>
3 <script type="text/javascript" th:src="@{/resources/spring/Spring-Dojo.js}"></script>
5 ...
7 <form id="triggerform" method="post" action="">
8 <input type="submit" id="doUpdate" name="_eventId_updateData" value="Update now!" />
9 </form>
11 <script type="text/javascript">
12 Spring.addDecoration(
13 new Spring.AjaxEventDecoration({
14 formId:'triggerform',
15 elementId:'doUpdate',
16 event:'onclick'
17 }));
18 </script>