模板引擎接口 ITemplateEngine

一、后台数据与外部数据

1.处理后台数据

$表达式是个变量表达式,用于处理在  request parameters and the request, session and application 中的变量

  • ${x} will return a variable x stored into the Thymeleaf context or as a request attribute.
  • ${param.x} will return a request parameter called x (which might be multivalued).
  • ${session.x} will return a session attribute called x.
  • ${application.x} will return a servlet context attribute called x.
<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 获取的值包含 < >等,默认会对其转义为 &lt; &gt;

比如:

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 &lt;b>fantastic&lt;/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) the HttpServletRequest object.
  • #response: (only in Web Contexts) the HttpServletResponse object.
  • #session: (only in Web Contexts) the HttpSession object.
  • #servletContext: (only in Web Contexts) the ServletContext 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 for java.util.Date objects: formatting, component extraction, etc.
  • #calendars: analogous to #dates, but for java.util.Calendar objects.
  • #numbers: methods for formatting numeric objects.
  • #strings: methods for String 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 collectionwith the same number of elements than the original onebut 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

https://spring.io/guides/gs/validating-form-input/

Java 前端模板引擎学习:thymeleaf 模板引擎的更多相关文章

  1. SpringBoot thymeleaf模板版本,thymeleaf模板更换版本

    SpringBoot thymeleaf模板版本 thymeleaf模板更换版本 修改thymeleaf模板版本 ================================ ©Copyright ...

  2. Thymeleaf模板引擎的初步使用

    在springboot中,推荐使用的模板引擎是Thymeleaf模板引擎,它提供了完美的Spring MVC的支持.下面就简单的介绍一下Thymeleaf模板引擎的使用. 在controller层中, ...

  3. 最简单的 springboot 发送邮件,使用thymeleaf模板

    1,导入需要的包 <dependency> <groupId>org.springframework.boot</groupId> <artifactId&g ...

  4. Thymeleaf模板引擎学习

    开发传统Java WEB项目时,我们可以使用JSP页面模板语言,但是在SpringBoot中已经不推荐使用JSP页面进行页面渲染了.从而Thymeleaf提供了一个用于整合Spring MVC的可选模 ...

  5. thymeleaf模板引擎调用java类中的方法(附源码)

    前言 <Docker+SpringBoot+Mybatis+thymeleaf的Java博客系统开源啦> 由于开源了项目的缘故,很多使用了My Blog项目的朋友遇到问题也都会联系我去解决 ...

  6. Thymeleaf 模板引擎简介

    目录 Thymeleaf 模板引擎 官方文档下载 Hello World 新建应用 后台控制器 前端页面 浏览器访问测试 Thymeleaf 模板引擎1.Thymeleaf 是 Web 和独立环境的现 ...

  7. 狂神说SpringBoot11:Thymeleaf模板引擎

    狂神说SpringBoot系列连载课程,通俗易懂,基于SpringBoot2.2.5版本,欢迎各位狂粉转发关注学习. 微信公众号:狂神说(首发)    Bilibili:狂神说Java(视频) 未经作 ...

  8. thymeleaf模板引擎shiro集成框架

    shiro权限框架.前端验证jsp设计.间tag它只能用于jsp系列模板引擎. 使用最近项目thymeleaf作为前端模板引擎,采用HTML档,未出台shiro的tag lib,假设你想利用这段时间s ...

  9. Volecity模板引擎学习笔记

    转自:https://blog.csdn.net/reggergdsg/article/details/50937433 最近项目中用到了volecity模板,这里做一下笔记,学习中...相比较 Fr ...

  10. Thymeleaf模板引擎+Spring整合使用方式的介绍

    尊重原创,原文地址为:https://www.cnblogs.com/jiangchao226/p/5937458.html 前言 这个教程介绍了Thymeleaf与Spring框架的集成,特别是Sp ...

随机推荐

  1. 深入浅出mybatis之启动详解

    深入浅出mybatis之启动详解 MyBatis功能丰富,但使用起来非常简单明了,今天我们来追踪一下它的启动过程. 目录 如何启动MyBatis 如何使用MyBatis MyBatis启动过程 如何启 ...

  2. SpringBoot系列: Web应用鉴权思路

    ==============================web 项目鉴权============================== 主要的鉴权方式有:1. 用户名/密码鉴权, 然后通过 Sess ...

  3. CentOS安装VLC

    For EL7: rpm -Uvh https://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-9.noarch.rpm rpm - ...

  4. tp学习笔记1

    1.在tp中,model实际表示的只是一条记录,所以是个一维数组.所有记录组成的记录集,是用collection对象表示的. 2.model有一个静态方法get,使用:model::get(),返回的 ...

  5. HT for Web框架使用心得

    一.简单介绍 在HT for Web的官网首页写着,构建先进2D和3D可视化所需要的一切. 是的,只要你看过官网,你就会知道,这是一个企业的.并非开源的且需要收费的框架. 因为公司的业务需要,且公司使 ...

  6. Laravel 开发环境搭建 - Windows

    Laravel 开发环境搭建 - Windows  :   https://laravel-china.org/docs/laravel-development-environment/5.5/dev ...

  7. 在Linux中调试段错误(core dumped)

    在Linux中调试段错误(core dumped) 在作比赛的时候经常遇到段错误, 但是一般都采用的是printf打印信息这种笨方法,而且定位bug比较慢,今天尝试利用gdb工具调试段错误. 段错误( ...

  8. BETA 版冲刺前准备(团队)

    过去存在的问题 文档问题 未能事先做好设计文档,且小程序与后端数据库接口存在问题 界面问题 原型图设计花的时间较少,小程序设计界面仍相对粗糙,前端成员css元素应用经验不足 小程序界面之间对接存在问题 ...

  9. MariaDB:SSL配置

    参考文章:https://blog.csdn.net/johnhill_/article/details/72831932 ,谢谢! 1.安装openssl 下载地址:http://slproweb. ...

  10. vue.js组件命名