模板引擎接口 ITemplateEngine
一、后台数据与外部数据
1.处理后台数据
$表达式是个变量表达式,用于处理在 request parameters and the request, session and application 中的变量
${x}
will return a variablex
stored into the Thymeleaf context or as a request attribute.${param.x}
will return a request parameter calledx
(which might be multivalued).${session.x}
will return a session attribute calledx
.${application.x}
will return a servlet context attribute calledx
.
<input type="text" name="userName" value="James Carrot" th:value="${user.name}" />
2.处理外部数据
外部化的片段通常叫作 messages,#表达式用于处理这类消息。外部消息可以从数据库中获取,或从 .properties
files 中获取,这取决于 StandardMessageResolver 的实现。Thymeleaf 的默认实现为 StandardMessageResolver。
<p th:text="#{home.welcome}">Welcome to our grocery store!</p>
为了实现属性名的 i18n,消息解析器 StandardMessageResolver 将/WEB-INF/templates/home.html
映射于同文件夹同名文 propreties 上,比如
/WEB-INF/templates/home_en.properties
for English texts./WEB-INF/templates/home_es.properties
for Spanish language texts./WEB-INF/templates/home_pt_BR.properties
for Portuguese (Brazil) language texts./WEB-INF/templates/home.properties
for default texts (if the locale is not matched).
注意在 spring boot 中,国际化消息直接映射到资源目录下,如 /i18n/ messages.properties 或 /i18n/ messages _en.properties
二、文本数据处理
1.不转义
如果 $-expression 获取的值包含 < >等,默认会对其转义为 < >
比如:
home.welcome=Welcome to our <b>fantastic</b> grocery store!
使用 th:text ,默认转义成返回
<p th:text="#{home.welcome}">Welcome to our grocery store!</p>
<p>Welcome to our <b>fantastic</b> grocery store!</p>
使用 th:utext 返回
<p th:utext="#{home.welcome}">Welcome to our grocery store!</p>
<p>Welcome to our <b>fantastic</b> grocery store!</p>
三、# 表达式
#{...} expressions 外部消息常用于实现国际化
<p th:utext="#{home.welcome}">Welcome to our grocery store!</p>
i18n:home.welcome=¡Bienvenido a nuestra tienda de comestibles!
你可以指定 #-expression 的 key 从 $-表达式获取:
<p th:utext="#{${welcomeMsgKey}">
Welcome to our grocery store, Sebastian Pepper!
</p>
#-expression 中夹带 变量进行结合输出,#{} 中使用 () 包裹传入的变量,多个变量使用 , 分隔
home.welcome=¡Bienvenido a nuestra tienda de comestibles, {0}!
<p th:utext="#{home.welcome(${session.user.name})}">
Welcome to our grocery store, Sebastian Pepper!
</p>
* 特殊情况
模板连接变量,形成消息
<td th:text="#{|seedstarter.type.${sb.type}|}">Wireframe</td>
消息变成变量,预处理
<p th:text="${__#{article.text('textVar')}__}">Some text here...</p>
四、$ 表达式
${...}
expressions 用于操作变量,基于 OGNL表达式 (在 springmvc 中使用 spel 代替 ognl),对其相应的 context中的 变量进行映射
为了更灵活地使用 ognl,也包含了 7 个预置对象,
#ctx
: the context object.#vars:
the context variables.#locale
: the context locale.#request
: (only in Web Contexts) theHttpServletRequest
object.#response
: (only in Web Contexts) theHttpServletResponse
object.#session
: (only in Web Contexts) theHttpSession
object.#servletContext
: (only in Web Contexts) theServletContext
object.
以 # 号开始以引用这些预置对象。示例:
Established locale country: <span th:text="${#locale.country}">US</span>.
* 内置对象的使用技巧:
访问 param, session, application 属性时,不需要加 #
// 直接访问对象域的属性
${param.foo} // Retrieves a String[] with the values of request parameter 'foo'
${param.number[0]} // Retrieves a String with the values of request parameter 'number',
${param.size()}
${param.isEmpty()}
${param.containsKey('foo')}
${session.foo} // 获取 session 中的属性
${session.size()}
${session.isEmpty()}
${session.containsKey('foo')}
${application.foo} // Retrieves the ServletContext atttribute 'foo'
${application.size()}
${application.isEmpty()}
${application.containsKey('foo')}
# 直接访问 javax.servlet.http.HttpSession 对象
${#session.getAttribute('foo')}
${#session.id}
${#session.lastAccessedTime}
v注意:返回的是一个字符串数组:
除了 7 个预置顶级对象,还有 16 个工具对象:
#execInfo
: information about the template being processed.#messages
: methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax.#uris
: methods for escaping parts of URLs/URIs#conversions
: methods for executing the configured conversion service (if any).#dates
: methods forjava.util.Date
objects: formatting, component extraction, etc.#calendars
: analogous to#dates
, but forjava.util.Calendar
objects.#numbers
: methods for formatting numeric objects.#strings
: methods forString
objects: contains, startsWith, prepending/appending, etc.#objects
: methods for objects in general.#bools
: methods for boolean evaluation.#arrays
: methods for arrays.#lists
: methods for lists.#sets
: methods for sets.#maps
: methods for maps.#aggregates
: methods for creating aggregates on arrays or collections.#ids
: methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).
工具对象的使用,请查看原文。 常用工具类对象的操作方法:
# 格式化数字
${#numbers.formatDecimal(num,3,2)} // 整数位显示位数与小数精确位数
${#numbers.formatCurrency(num)} // 货币
${#numbers.formatPercent(num, 3, 2)} // 百分号
# 格式化日期
${#dates.second(date)}
${#dates.createNow()}
${#dates.create(year,month,day)}
${#dates.format(date, 'dd/MMM/yyyy HH:mm')}
# 操作字符串
${#strings.toString(obj)} //打印对象
${#strings.trim(str)} //去除首尾空白
${#strings.containsIgnoreCase(name,'ez')} // 判断是否包含
${#strings.startsWith(name,'Don')} // also array*, list* and set*
${#strings.endsWith(name,endingFragment)} // 判断前缀与后缀
${#strings.abbreviate(str,10)} // 大于此位数内容,以省略号 ...代替
${#strings.indexOf(name,frag)}
${#strings.substring(name,3,5)}
${#strings.substringAfter(name,prefix)}
${#strings.substringBefore(name,suffix)} // 截取字符串
${#strings.replace(name,'las','ler')} // 替换字符串
${#strings.arrayJoin(namesArray,',')}
${#strings.listJoin(namesList,',')}
${#strings.setJoin(namesSet,',')}
${#strings.toUpperCase(name)} // also array*, list* and set*
${#strings.toLowerCase(name)}
${#strings.capitalizeWords(str)} //首字母大写
${#strings.arraySplit(namesStr,',')} // 返回 String[],用于遍历
# 聚集函数
${#aggregates.sum(array)} // 求何
${#aggregates.avg(array)} // 平均数
#随机数
${#strings.randomAlphanumeric(count)}
#遍历数字的工具
${#numbers.sequence(from,to)}
${#numbers.sequence(from,to,step)}
# 类型转换
${#conversions.convert(object, 'java.util.TimeZone')}
#转义字符串,防止JavaScript or JSON 注入
${#strings.escapeXml(str)}
${#strings.escapeJava(str)}
${#strings.escapeJavaScript(str)}
${#strings.unescapeJava(str)}
${#strings.unescapeJavaScript(str)}
五、* 表达式
当对象被选择(使用 th:object )后,可以使用 *{} 获取对象属性
<div th:object="${session.user}">
<p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>
下面几处用法效果等同
<div th:object="${session.user}">
<p>Name: <span th:text="${#object.firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>
此外,如果没有对象被选择,则 *-expression 与 $-expression 完全相当
<div>
<p>Name: <span th:text="*{session.user.name}">Sebastian</span>.</p>
<p>Surname: <span th:text="*{session.user.surname}">Pepper</span>.</p>
<p>Nationality: <span th:text="*{session.user.nationality}">Saturn</span>.</p>
</div>
六、A 标签处理
1.标签 th:href 与 @{} expressions
<!-- Will produce 'http://localhost:8080/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html"
th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a>
<!-- Will produce '/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>
<!-- Will produce '/gtvg/order/3/details' (plus rewriting) -->
<a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>
* 为什么使用 th:href="@{}"
使用模板引擎管理 link 地址,方便对链接进行额外处理(如版本处理之类)
2.注意不可变部分,需要作预处理
<a href="#" th:href="@{__${#request.requestURI}__/learn}">Learn</a>
七、一些特殊的表达式语法
① 模板字符串代替 + 连接
<span th:text="'Welcome to our application, ' + ${user.name} + '!'">
<span th:text="|Welcome to our application, ${user.name}!|">
下文 https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#expressions-on-selections-asterisk-syntax
②预编译(预处理)
<tr th:each="book, itemStat : *{books}">
<td><input th:field="*{books[__${itemStat.index}__].title}" /></td>
<td><input th:field="*{books[__${itemStat.index}__].author}" /></td>
</tr>
也可以拆开写成 th:name 与 th:value (不推荐)
<tr th:each="book, itemStat : ${form.books}">
<td><input hidden th:name="|books[${itemStat.index}].id|" th:value="${book.getId()}"/></td>
<td><input th:name="|books[${itemStat.index}].title|" th:value="${book.getTitle()}"/></td>
<td><input th:name="|books[${itemStat.index}].author|" th:value="${book.getAuthor()}"/></td>
</tr>
③ 代数运算,可以使用 div
(/
), mod
(%
). 等
<div th:with="isEven=(${prodStat.count} % 2 == 0)">
<div th:with="isEven=${prodStat.count % 2 == 0}">
④ 比较运算,可以使用 gt
(>
), lt
(<
), ge
(>=
), le
(<=
), not
(!
). Also eq
(==
), neq
/ne
(!=
)
<div th:if="${prodStat.count} > 1">
<span th:text="'Execution mode is ' + ( (${execMode} == 'dev')? 'Development' : 'Production')">
⑤ 条件表达式,其中 else 部分也可以忽略不写,则当为否时返回 null
<tr th:class="${row.even}? 'even' : 'odd'">
...
</tr>
<tr th:class="${row.even}? 'alt'">
...
</tr>
⑥ 默认值表达式,当变量没有赋任何值时,使用该默认值
<div th:object="${session.user}">
...
<p>Age: <span th:text="*{age}?: '(no age specified)'">27</span>.</p>
</div>
也可以在子表达式中,效果类似于:
<p>
Name:
<span th:text="*{firstName}?: (*{admin}? 'Admin' : #{default.username})">Sebastian</span>
</p>
⑦ 无操作符号 no-operation token ,即使用当前文字节点
<span th:text="${user.name} ?: _">no user authenticated</span>
相当于
<span th:text="${user.name} ?: 'no user authenticated'">...</span>
⑧ 自动应用转换服务
服务端配置文件 —— application.yml (这种配置只在页面格式化管用,在向 controller 传参时并不管用)
spring:
mvc:
date-format: yyyy-mm-dd
或 JavaCofig (页面和传参都管用)
@Override
public void addFormatters(final FormatterRegistry registry) {
super.addFormatters(registry);
registry.addFormatter(varietyFormatter());
registry.addFormatter(dateFormatter());
}
@Bean
public VarietyFormatter varietyFormatter() {
return new VarietyFormatter();
}
@Bean
public DateFormatter dateFormatter() {
return new DateFormatter();
}
页面使用 双括号 {{ 与 }}
<td th:text="${{sb.datePlanted}}">13/01/2011</td>
⑨ 静态方法调用与 Bean 方法调用
将字符串转换为 Integer
T(Integer).parseInt(param.pagenum)
或者引用自定义工具类
th:text="${T(com.package.SUtils).reverseString('hello')}"
使用 @引用 Bean
<span th:text="${@sUtils.reverseString('hello')}"></span>
⑩ 对集合数组进行 map 与 filter 处理
- selection means filtering each element of the collection using its properties. The result of such an operation is a subset of the initial collection, only matching elements are retained
.
- projection means filtering the property of each element in the collection. The result of a projection operation is a a new collection, with the same number of elements than the original one, but containing only the filtered property of the elements, not the whole element object itself
示例:
map ( projection )
<tr th:each="artist,rowStat : ${listArtits.![firstname+' '+lastname]}">
<td class="center middle" th:text="${rowStat.count}">1</td>
<td class="center middle" th:text="${artist}">Michael Jackson</td>
</tr>
filter ( selection )
<tr th:each="artist,rowStat : ${listArtits.?[alive == true]}">
<td class="center middle" th:text="${rowStat.count}">1</td>
<td class="center middle" th:text="${artist.name}">Michael Jackson</td>
<td class="center middle" th:text="${artist.discography}">Got to Be There, Ben, Music & Me, Forever Michael...</td>
<td class="center middle" th:text="${artist.bio}">Michael Joseph Jackson (August 29, 1958 - June 25, 2009) was an American recording artist, entertainer, and businessman...</td>
</tr>
八、操作标签的属性值
th:attr
<form action="subscribe.html" th:attr="action=@{/subscribe}">
<fieldset>
<input type="text" name="email" />
<input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
</fieldset>
</form>
多个属性用逗号分开
<img src="../../images/gtvglogo.png"
th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
<img src="/gtgv/images/gtvglogo.png" title="Logo de Good Thymes" alt="Logo de Good Thymes" />
或者属性联写
<img src="../../images/gtvglogo.png"
th:src="@{/images/gtvglogo.png}" th:alt-title="#{logo}" />
也可以直接使用 th:value 、 th:src、th:action 特殊属性(见 setting-value-to-specific-attributes )
<img src="../../images/gtvglogo.png"
th:src="@{/images/gtvglogo.png}" th:title="#{logo}" th:alt="#{logo}" />
or
<img src="../../images/gtvglogo.png"
th:src="@{/images/gtvglogo.png}" th:alt-title="#{logo}" />
增加属性值(Appending and prepending)
<input type="button" value="Do it!" class="btn" th:attrappend="class=${' ' + cssStyle}" />
如果 cssStyle 是 warning 的话,则 class 会增加一个值
<input type="button" value="Do it!" class="btn warning" />
也可是使用更简便的 th:classappend
<tr th:each="prod : ${prods}" class="row" th:classappend="${prodStat.odd}? 'odd'">
九、遍历迭代
普通的迭代
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr> <tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
指定迭代状态量 th:each="prod,iterStat : ${prods}"
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
</tr>
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
</table>
注意状态的几种属性
index: the current iteration index, starting with 0 (zero)
count: the number of elements processed so far
size: the total number of elements in the list
even/odd: checks if the current iteration index is even or odd
first: checks if the current iteration is the first one
last: checks if the current iteration is the last one
* 如果不手动设定,可以使用默认实现的 状态变量后缀Stat,比如这里的 prodStat
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
</tr>
<tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
</table>
* 如果遍历的内容需要更多 tr标签,可以使用 th:block,比如:
<table>
<th:block th:each="user : ${users}">
<tr>
<td th:text="${user.login}">...</td>
<td th:text="${user.name}">...</td>
</tr>
<tr>
<td colspan="2" th:text="${user.address}">...</td>
</tr>
</th:block>
</table>
渲染结果
<table>
<!--/*/ <th:block th:each="user : ${users}"> /*/-->
<tr>
<td th:text="${user.login}">...</td>
<td th:text="${user.name}">...</td>
</tr>
<tr>
<td colspan="2" th:text="${user.address}">...</td>
</tr>
<!--/*/ </th:block> /*/-->
</table>
十、判断语句
th:if
- If value is a boolean and is
true
. - If value is a number and is non-zero
- If value is a character and is non-zero
- If value is a String and is not “false”, “off” or “no”
- If value is not a boolean, a number, a character or a String.
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>
相反 th:unless
<a href="comments.html"
th:href="@{/comments(prodId=${prod.id})}"
th:unless="${#lists.isEmpty(prod.comments)}">view</a>
或者,使用 switch-case 语句
<div th:switch="${user.role}">
<p th:case="'admin'">User is an administrator</p>
<p th:case="#{roles.manager}">User is a manager</p>
<p th:case="*">User is some other thing</p>
</div>
十一、模板布局
1.基本用法
使用 th:fragment 定义 partial 模板
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</div>
</body>
</html>
使用 th:insert 或 th:replace 插入模板(th:include 也可以,但在 3.0 后并不建议使用)
<body>
...
<div th:insert="footer :: copy"></div>
</body>
或者也可以给引号部分加一个 ~{} ,将器封装起来
<body>
...
<div th:insert="~{footer :: copy}"></div>
</body>
2. th:insert 、th:replace 与 th:include 区别
定义模板
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</footer>
使用 div 标签引用模板
<body>
...
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>
</body>
分别渲染出的结果:
<body>
...
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
</div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
<div>
© 2011 The Good Thymes Virtual Grocery
</div>
</body>
得出结论:
- th:insert 包括自身标签和子标签,
- th:replace 包括子标签但不包括自身标签,
- th:include 则是包括自身标签不包括子标签。
3.没有 th:fragment
定义模板
...
<div id="copy-section">
© 2011 The Good Thymes Virtual Grocery
</div>
...
使用 id 属性引用该模板
<body>
...
<div th:insert="~{footer :: #copy-section}"></div>
</body>
4.模板中传递参数
① 普通变量
定义模板
<div th:fragment="frag (onevar,twovar)">
<p th:text="${onevar} + ' - ' + ${twovar}">...</p>
</div>
调用模板,并传递相应的变量(注意这里调用的是当前模板文件中的 fragment,因此可以省略写成 th:replace=":: 函数名称 "
<div th:replace="::frag (${value1},${value2})">...</div>
② 局部变量
定义模板
<div th:fragment="frag">
...
</div>
传递局部变量
<div th:replace="::frag (onevar=${value1},twovar=${value2})">
上面的表达式,相当于
<div th:insert="::frag" th:with="onevar=${value1},twovar=${value2}">
5.常见的技巧
① head中引用 title 与 link 块
定义模板
<head th:fragment="common_header(title,links)">
<title th:replace="${title}">The awesome application</title>
<!-- Common styles and scripts -->
<link rel="stylesheet" type="text/css" media="all" th:href="@{/css/awesomeapp.css}">
<link rel="shortcut icon" th:href="@{/images/favicon.ico}">
<script type="text/javascript" th:src="@{/sh/scripts/codebase.js}"></script>
<!--/* Per-page placeholder for additional links */-->
<th:block th:replace="${links}" />
</head>
调用模板(注意这里调用的模板文件名称是 base
...
<head th:replace="base :: common_header(~{::title},~{::link})">
<title>Awesome - Main</title>
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
<link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">
</head
渲染结果
...
<head>
<title>Awesome - Main</title>
<!-- Common styles and scripts -->
<link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css">
<link rel="shortcut icon" href="/awe/images/favicon.ico">
<script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script>
<link rel="stylesheet" href="/awe/css/bootstrap.min.css">
<link rel="stylesheet" href="/awe/themes/smoothness/jquery-ui.css">
</head>
...
* 传递到模板的参数,除了使用 ${} 符号引入变量,或者使用 ~{:: 嵌入当前的标签,还可以使用 no-operation token 或者 参数留空。比如:
无操作符号 no-operation token
...
<head th:replace="base :: common_header(_,~{::link})">
<title>Awesome - Main</title>
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
<link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">
</head>
...
参数留空
<head th:replace="base :: common_header(~{::title},~{})">
<title>Awesome - Main</title>
</head>
...
② 条件调用模板
根据条件决定是否调用模板
...
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : ~{}">...</div>
...
或者条件不满足,调用默认模板
...
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : _">
Welcome [[${user.name}]], click <a th:href="@{/support}">here</a> for help-desk support.
</div>
...
③ 隐藏或移除 mock 数据(感觉跟 fragment 没多大关系)
通常在我们会定义一些 mock 数据,用来做静态网页测试,查看渲染效果。像下面这样后两段 <tr> 作为 mock 数据。
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
<th>COMMENTS</th>
</tr>
<tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
<td>
<span th:text="${#lists.size(prod.comments)}">2</span> comment/s
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:unless="${#lists.isEmpty(prod.comments)}">view</a>
</td>
</tr>
<tr class="odd" th:remove="all">
<td>Blue Lettuce</td>
<td>9.55</td>
<td>no</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr th:remove="all">
<td>Mild Cinnamon</td>
<td>1.99</td>
<td>yes</td>
<td>
<span>3</span> comment/s
<a href="comments.html">view</a>
</td>
</tr>
</table>
由于使用 th:remove 标记,该内容将在服务端渲染时被移除。
移除的形式有很多种:
all
: Remove both the containing tag and all its children.body
: Do not remove the containing tag, but remove all its children.tag
: Remove the containing tag, but do not remove its children.all-but-first
: Remove all children of the containing tag except the first one.none
: Do nothing. This value is useful for dynamic evaluation.
鉴于此,在 tbody 标签上使用 all-but-first
<table>
<thead>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
<th>COMMENTS</th>
</tr>
</thead>
<tbody th:remove="all-but-first">
<tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
<td>
<span th:text="${#lists.size(prod.comments)}">2</span> comment/s
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:unless="${#lists.isEmpty(prod.comments)}">view</a>
</td>
</tr>
<tr class="odd">
<td>Blue Lettuce</td>
<td>9.55</td>
<td>no</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr>
<td>Mild Cinnamon</td>
<td>1.99</td>
<td>yes</td>
<td>
<span>3</span> comment/s
<a href="comments.html">view</a>
</td>
</tr>
</tbody>
</table>
* 根据条件移除
<a href="/something" th:remove="${condition}? tag">Link text not to be removed</a>
④ 单独文件作为 partial 模板
定义模板
<!DOCTYPE html>
<html th:fragment="layout (title, content)" xmlns:th="http://www.thymeleaf.org">
<head>
<title th:replace="${title}">Layout Title</title>
</head>
<body>
<h1>Layout H1</h1>
<div th:replace="${content}">
<p>Layout content</p>
</div>
<footer>
Layout footer
</footer>
</body>
</html>
调用模板,并传入相应的参数
<!DOCTYPE html>
<html th:replace="~{layoutFile :: layout(~{::title}, ~{::section})}">
<head>
<title>Page Title</title>
</head>
<body>
<section>
<p>Page content</p>
<div>Included on page</div>
</section>
</body>
</html>
十二、局部变量
定义一个或多个局部变量,并使用它们
<div th:with="firstPer=${persons[0]},secondPer=${persons[1]}">
<p>
The name of the first person is <span th:text="${firstPer.name}">Julius Caesar</span>.
</p>
<p>
But the name of the second person is
<span th:text="${secondPer.name}">Marcus Antonius</span>.
</p>
</div>
一个实用的例子,比如根据 i18n 文件读取格式化方式,来格式化日期,比如:
home_en.properties
date.format=MMMM dd'','' yyyy
home_es.properties
date.format=dd ''de'' MMMM'','' yyyy
定义局部变量,并引用它
<p th:with="df=#{date.format}">
Today is: <span th:text="${#calendars.format(today,df)}">13 February 2011</span>
</p>
或者
<p>
Today is:
<span th:with="df=#{date.format}"
th:text="${#calendars.format(today,df)}">13 February 2011</span>
</p>
十三、特殊页面处理
1. 分页页面
2. 表单页面
① 基本用法
<form action="#" th:action="@{/seedstartermng}" th:object="${seedStarter}" method="post">
<input type="text" th:field="*{datePlanted}" />
...
</form>
checkbox 处理(需要传两个对象,一个是全部属性 allFeatures,一个是当前选择的 features)
<ul>
<li th:each="feat : ${allFeatures}">
<input type="checkbox" th:field="*{features}" th:value="${feat}" />
<label th:for="${#ids.prev('features')}"
th:text="#{${'seedstarter.feature.' + feat}}">Heating</label>
</li>
</ul>
radio 处理
<ul>
<li th:each="ty : ${allTypes}">
<input type="radio" th:field="*{type}" th:value="${ty}" />
<label th:for="${#ids.prev('type')}" th:text="#{${'seedstarter.type.' + ty}}">Wireframe</label>
</li>
</ul>
dropdown 处理
<select th:field="*{type}">
<option th:each="type : ${allTypes}"
th:value="${type}"
th:text="#{${'seedstarter.type.' + type}}">Wireframe</option>
</select>
② 动态 field:
需求: 当 command 对象有一个 collection property,对这个属性进行遍历,需要生成不同的 field
示例:
创建书籍(显示页面)
@GetMapping("/create")
public String showCreateForm(Model model) {
BooksCreationDto booksForm = new BooksCreationDto();
for (int i = 1; i <= 3; i++) {
booksForm.addBook(new Book());
}
model.addAttribute("form", booksForm);
return "books/createBooksForm";
}
表单页面
<form action="#" th:action="@{/books/save}" th:object="${form}"
method="post">
<fieldset>
<input type="submit" id="submitButton" th:value="Save">
<input type="reset" id="resetButton" name="reset" th:value="Reset"/>
<table>
<thead>
<tr>
<th> Title</th>
<th> Author</th>
</tr>
</thead>
<tbody>
<tr th:each="book, itemStat : *{books}">
<td><input th:field="*{books[__${itemStat.index}__].title}" /></td>
<td><input th:field="*{books[__${itemStat.index}__].author}" /></td>
</tr>
</tbody>
</table>
</fieldset>
</form>
* 说明:
上面的模板通过 * 表达式选择该 集合属性,然后使用__ 预处理__表达式生成不同的 field(即带有数组样式的 id 与 name)渲染结果类似
<input id="rows1.seedsPerCell" name="rows[1].seedsPerCell" type="text" value="" />
添加书籍(提交页面)
@PostMapping("/save")
public String saveBooks(@ModelAttribute BooksCreationDto form, Model model) {
bookService.saveAll(form.getBooks());
model.addAttribute("books", bookService.findAll());
return "redirect:/books/all";
}
③ 表单验证(* 关于 controller 部分请参考文末参考链接)
判断错误是否存在
<input type="text" th:field="*{datePlanted}"
th:class="${#fields.hasErrors('datePlanted')}? fieldError" />
或者直接使用 th:errorclass
<input type="text" th:field="*{datePlanted}" class="small" th:errorclass="fieldError" />
遍历该 field 的错误
<li th:each="err : ${#fields.errors('datePlanted')}" th:text="${err}" />
直接输出该 field 的错误
<p th:if="${#fields.hasErrors('datePlanted')}" th:errors="*{datePlanted}">Incorrect date</p>
获取全部 field 错误
<div th:if="${#fields.hasAnyErrors()}">
<p th:each="err : ${#fields.allErrors()}" th:text="${err}">...</p>
</div>
注意:
#fields.hasErrors('*') <=> #fields.hasAnyErrors() #fields.errors('*') <=> #fields.allErrors()
或通过 detailedErrors 详细遍历
<ul>
<li th:each="e : ${#fields.detailedErrors()}" th:class="${e.global}? globalerr : fielderr">
<span th:text="${e.global}? '*' : ${e.fieldName}">The field name</span> |
<span th:text="${e.message}">The error message</span>
</li>
</ul>
233
参考链接
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html
https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html#integrating-thymeleaf-with-spring