0
点赞
收藏
分享

微信扫一扫

Thymeleaf3.0简易教程

早安地球 2022-08-24 阅读 20


​​Demo下载​​,可以在这个demo上进行练习。

1.概述

SpringBoot支持的模板引擎:

  • Thymeleaf(SpringBoot推荐)
  • FreeMarker
  • Velocity
  • Groovy
  • JSP(以前开发Java Web时用的,现在不推荐)
    前后端分离开发虽然已普遍存在,Thymeleaf也可以很好的进行前后台的协作开发。

使用Thymeleaf 三大理由:

  1. 简洁漂亮 容易理解
  2. 完美支持HTML5 使用浏览器直接打开页面
  3. 不新增标签 只需增强属性

Thymeleaf支持处理六种模板:

  • HTML(默认)
  • XML
  • TEXT
  • JAVASCRIPT
  • CSS
  • RAW

2.添加Thymeleaf依赖

使用Thymeleaf需要在pom.xml文件中添加依赖:

<properties>
<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
<thymeleaf-layout-dialect.version>2.4.1</thymeleaf-layout-dialect.version>
</properties>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

3.SpringBoot配置Thymeleaf

在application.yml或application.properties进行配置

# 是否开启缓存,开发中设置成false,便于更改文件后,自动刷新
spring.thymeleaf.cache=false
# 检查模板是否存在
spring.thymeleaf.check-template=true
# 是否启用thymeleaf作为视图解析
spring.thymeleaf.enabled=true
# 是否spring el 表达式
spring.thymeleaf.enable-spring-el-compiler=false
# 模板文件编码
spring.thymeleaf.encoding=UTF-8
# 指定不解析的视图名以逗号分隔,
spring.thymeleaf.excluded-view-names=
# 解析的模板类型
spring.thymeleaf.mode=HTML
# 模板文件路径前缀
spring.thymeleaf.prefix=classpath:/templates/
# 输出类型
spring.thymeleaf.servlet.content-type=text/html
# 文件后缀
spring.thymeleaf.suffix=.html
#使用在thymeleaf使用消息表达式语法时#{xx},需指定文件名
spring.messages.basename=application

4.模板页面

SpringBoot默认存放页面模板的路径在​​src/main/resources/templates​​​或者​​src/main/view/templates​​​,Thymeleaf默认的页面文件后缀是.html。
Thymeleaf完美支持HTML5 使用浏览器直接打开页面,因为浏览器会忽略它不认识的属性。绝大多数Standard Dialect处理器都是属性处理器。Thymeleaf中很多属性都是​​​th:*​​​这种形式的,这种不是标准的HTML属性,但是HTML5允许自定义以data-为前缀的属性,将其变成​​data-th-*​​就会变成合法的属性。

5.标准表达式语法

所有这些表达式都可以组合和嵌套。

5.1.简单表达式

5.1.1.#{…}消息表达式

外部化文本就是从模板文件中提取的模板代码片段,它们可以分开保存,典型的就是保存在.properties文件。因此它们可以被轻易地用其他语言的相应的文本来代替,这就是国际化的处理。外部化文本片段通常被称为“message”消息。消息都有一个key来识别它们。Thymeleaf允许我们用#{…}指定text对应的消息。消息表达式的消息会被抽取出来放单独放在.properties文件中。消息表达式在国际化中最常用。国际化的例子可以参考:​​《SpringBoot+Thymeleaf实现国际化》​​

5.1.2.${…}变量表达式

在模板中从WebContext获取请求参数和请求、会话、属性:

${x}:返回一个存储在Thymeleaf Context中的变量x,或者作为一个请求属性

${param.x}:返回一个请求参数x(可能是多个值的)

${session.x}:返回一个会话属性x

${application.x}:返回一个servlet context的属性x

举例:

home.html

<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="#{home.title}">Insert title here</title>
</head>
<body>
<div>
<a data-th-href="@{/locale(lang=zh_CN)}" th:text="#{home.language.chinese}">中文</a>
<a data-th-href="@{/locale(lang=en_US)}" th:text="#{home.language.english}">英语</a>
</div>
<h1 data-th-text="#{home.welcome(${userName})}">Fluid jumbotron</h1>
<h1 data-th-text="#{home.introduction(${userBean.name},${userBean.job},${userBean.hobby})}">Hello everyone!</h1>
<h1 data-th-text="${session.mark}">mark</h1>
<h1 data-th-text="${application.remark}">haha</h1>
</body>
</html>

HomeController.java

@Controller
public class HomeController{

@RequestMapping(value = {"/index","/home","/"},method = RequestMethod.GET)
public String getHomePage(Model model, Locale locale, HttpServletRequest request) {
// ${x}
model.addAttribute("userName","Tome");
// ${param.x}
UserBean userBean = new UserBean("Tome","IT Designer","play video game");
model.addAttribute("userBean",userBean);

// ${session.x}
HttpSession session=request.getSession();//获取session并将mark存入session对象
session.setAttribute("mark", "欢迎大驾光临!");

// ${application.x}
ServletContext application = request.getServletContext();//获取ServletContext并将remark存入ServletContext对象
application.setAttribute("remark","ByeBye!");

return "home";
}
}

th:text默认会转义特殊字符,th:utext(unescaped text)则不会发生转义,如 :

home.properties

home.general.introduction=Welcome to our <b>fantastic</b>

home.html

// 发生转义:Welcome to our <b>fantastic</b> grocery store!
<p data-th-text="#{home.general.introduction}">good</p>
// 不转义(实际效果中fantastic会被加粗):Welcome to our fantastic grocery store!
<p data-th-utext="#{home.general.introduction}">good</p>

5.1.3.*{…}选择变量表达式

选择表达式就是表达式的结果使用​​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>
<p>Name: <span th:text="${session.user.firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="${session.user.nationality}">Saturn</span>.</p>
</div>

星号和美元符号语法也是可以混用的:

<div th:object="${session.user}">
<p>Name: <span th:text="*{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>

当一个对象选择所在位置,内部可以用#object来代表它:

<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>

当没有对象选择时,美元符号和星号语法是等价的:

<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>

也就是说,如果没有对象选择被执行,那么${…}与*{…}是等价的:

<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>

5.1.4.@{…}链接URL表达式

在web应该模板中,URL是很重要的。Thymeleaf专门有一个特殊的@{…}语法来处理它们。这个新语法将用在​​th:href​​属性上。

URL类型:

  • 绝对URL:http://www.thymeleaf.org
  • 相对URL:
    (1)相对的页面:user/login.html
    (2)相对的上下文(在服务器的上下文名称会被自动添加):/itemdetails?id=3
    (3)相对的服务器(在同一个服务器上的另一个上下文(相当于另一个应用)里,允许调用URL):~/billing/processInvoice
    (4)协议相对URL: //code.jquery.com/query-2.3.0.min.js

这些URL表达式的真实处理和转换都会由Thymeleaf模板引擎处理完成输出。

举例:

<!-- 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>

注意:

  1. @{…}用在​​th:href​​上,将会替换掉<a>标签的href属性值
  2. 可以给表达式给URL添加参数,如​​orderId=${o.id}​​,如果有多参数,可以使用逗号隔开​​@{/order/process(execId=${execId},execType='FAST')}​
  3. 变量模板也允许应用在URL上,如​​@{/order/{orderId}/details(orderId=${orderId})}​
  4. 以/开头 (如: /order/details )的相对URL,将自动以应用程序上下文名称作为前缀。
  5. 如果cookie没有启用或未知,​​;jsessionid=...​​后缀可能会添加到相对URL,因此会话会被保留。这叫URL重写。Thymeleaf允许插入自己的URL重写,通过Servlet API​​response.encodeURL(...)​​机制。

URL也可以是其他表达式的计算值:

<a th:href="@{${url}(orderId=${o.id})}">view</a>
<a th:href="@{'/details/'+${user.login}(orderId=${o.id})}">view</a>

还有一个额外的语法用来创建server-root-relative(默认是context-root-relative)URL,目的是在同一服务器中,链接到不同的上下文。这样的URL形如:​​@{~/path/to/something}​

5.1.5.~{…}片段表达式

片段表达式可以让我们将模板公共部分抽取出来作为片段,然后在需要的地方引入。引入片段通过​​th:insert​​​或​​th:replace​​​来完成,其中​​th:include​​在Thymeleaf3.0开始不再推荐使用,因此不做介绍。

<div th:insert="~{templateName :: fragmentName}"></div>

<div th:replace="~{templateName :: fragmentName}"></div>

包含模板templateName的标记fragmentName。也可以将整个模板templateName包含进来:

<div th:insert="~{templateName}"></div>

还可以插入当前模板里的片段:

<div th:replace="~{this :: fragmentName}"></div>

<div th:replace="~{ :: fragmentName}"></div>

** 使用th:fragment="fragment"定义片段 **:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div data-th-fragment="copy">
© 2020 The W Grocery Store
</div>
</body>
</html>

除了这种方式外,还可以直接使用html的id,如:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="copy-section">
© 2020 The W Grocery Store
</div>
</body>
</html>

如果用了这种方式,那么~{…}表达式就要这样写:

<body>
...
<div th:insert="~{footer :: #copy-section}"></div>
</body>

6.常量

  • 文字常量:如’one text’,‘another one’,…
  • 数字常量:如1,34,12.3,…
  • 布尔常量:true、false

<div data-th-if="${user.isAdmin()} == false">hello</div>

user对象的isAdmin()方法,​​==false​​写在大花括号外,则由Thymeleaf处理,而写成大花括号内,则将由OGNL/SpringEL引擎来处理,即:

<div data-th-if="${user.isAdmin() == false}">hello</div>

  • Null常量:null

<div th:if="${variable.something} == null"> </div>

  • 字面标记:如one、sometext,main,…
    事实上数字、布尔常量、null都是字面标记的一种特殊形式,字面标记可以由字母、数字、方括号、点、连字符、下划线组成,不能用空格、逗号。字面标记不需要任何绰号括住:

<div th:class="content">...</div>

7.文本操作

  • 字符串连接:+

<span th:text="'The name of the user is ' + ${user.name}">

  • 文本替换:|The name is {name}|

<span th:text="|Welcome to our application, ${user.name}!|">

等价于

<span th:text="'Welcome to our application, ' + ${user.name} + '!'">

用了文本替换,就可以不用字符串连接符+将字符串连接起来了,非常方便。但是请注意,只有变量/消息表达式${…},*{…},#{…}可以出现在文本替换|…|中,其他文字(’…’ )、布尔/数学标记,条件表达式等都不能出现在其中。

8.算术操作符

  • 二元操作符:+,-,*,/,%

<div th:with="isEven=(${prodStat.count} % 2 == 0)">

以上操作将是Thymeleaf标准表达式引擎处理,如果​​% 2 == 0​​出现在大花括号内,则由OGNL/SpringEL引擎来处理:

<div th:with="isEven=${prodStat.count % 2 == 0}">

  • 一元操作符:-

9.比较和相等

  • 比较:>,<,>=,<= (gt,lt,ge,le)
    因为<,>是html标记的组成部分,不允许出现在属性部分,因此用&gt; &lt;

<div th:if="${prodStat.count} > 1">
<span th:text="'Execution mode is ' + ( (${execMode} == 'dev')? 'Development' : 'Production')">

  • 相等:==,!= (eq,ne)

10.条件操作符

  • if-then:(if) ? (then)

<tr data-th-each="product,dd : ${products}" data-th-class="${dd.odd}? 'odd'">

  • if-then-else:(if) ? (then) : (else)
    条件表达式,这三部分可以是变量(${,},*{…}),消息(#{…}),URL(@{…})或文本(’…’)。

<tr th:class="${row.even}? 'even' : 'odd'">
...
</tr>

条件表达式也可以嵌套:

<tr th:class="${row.even}? (${row.first}? 'first' : 'even') : 'odd'">
...
</tr>

甚至else部分也可省略,这种情况下,如果条件为false就返回:

<tr th:class="${row.even}? 'alt'">
...
</tr>

  • default:(value) ?: (defaultvalue)
    默认表达式是一种特殊的条件表达式,当value是null时,就使用defaultvalue。

<div th:object="${session.user}">
...
<p>Age: <span th:text="*{age}?: '(no age specified)'">27</span>.</p>
</div>

等价于

<p>Age: <span th:text="*{age != null}? *{age} : '(no age specified)'">27</span>.</p>

还可在默认值部分嵌套其他条件表达式:

<p>
Name:
<span th:text="*{firstName}?: (*{admin}? 'Admin' : #{default.username})">Sebastian</span>
</p>

11.特殊标记

  • 无操作:_

不做任何操作

<span th:text="${user.name} ?: _">no user authenticated</span>

等价于

<span th:text="${user.name} ?: 'no user authenticated'">...</span>

12.表达式基本对象

在解释模板时,会生成一些对象,在表达式中使用会使得表达式更加灵活。这些对象使用#号来引用:

  • #ctx:上下文对象

/*
* ======================================================================
* See javadoc API for class org.thymeleaf.context.IContext
* ======================================================================
*/
${#ctx.locale}
${#ctx.variableNames}
/*
* ======================================================================
* See javadoc API for class org.thymeleaf.context.IWebContext
* ======================================================================
*/
${#ctx.request}
${#ctx.response}
${#ctx.session}
${#ctx.servletContext}

  • #vars 和 #root:与#ctx都是同一个对象,但推荐使用#ctx
  • #locale:上下文区域,直接访问关联当前请求的java.util.Locale对象

${#locale}

  • Web上下文对象
  • #request:直接访问关联当前请求的javax.servlet.http.HttpServletRequest对象

${#request.getAttribute('foo')}
${#request.getParameter('foo')}
${#request.getContextPath()}
${#request.getRequestName()}
...

  • #session : 直接访问关联当前请求的javax.servlet.http.HttpSession对象

${#session.getAttribute('foo')}
${#session.id}
${#session.lastAccessedTime}
...

  • #servletContext : 直接访问关联当前请求的javax.servlet.ServletContext对象

${#servletContext.getAttribute('foo')}
${#servletContext.contextPath}
...

  • 为Web上下文命令空间提供的request/session属性
  • param:获取请求参数

/*
* ============================================================================
* See javadoc API for class org.thymeleaf.context.WebRequestParamsVariablesMap
* ============================================================================
*/
${param.foo}
// Retrieves a String[] with the values of request parameter 'foo'
${param.size()}
${param.isEmpty()}
${param.containsKey('foo')}
...

  • session:获取会话属性

/*
* ======================================================================
* See javadoc API for class org.thymeleaf.context.WebSessionVariablesMap
* ======================================================================
*/
${session.foo}
${session.size()}
${session.isEmpty()}
${session.containsKey('foo')}
...

  • application:获取application/servlet上下文属性

/*
* =============================================================================
* See javadoc API for class org.thymeleaf.context.WebServletContextVariablesMap
* =============================================================================
*/
${application.foo}
// Retrieves the ServletContext atttribute 'foo'
${application.size()}
${application.isEmpty()}
${application.containsKey('foo')}
...

13.表达式工具类对象

Thymeleaf提供很多工具类对象,帮助我们在表达式中完成一些常见的任务。

  • #execInfo : 提供在Thymeleaf标准表达式中被处理的模板的相关信息的表达式对象

/*
* Return the name and mode of the 'leaf' template. This means the template
* from where the events being processed were parsed. So if this piece of
* code is not in the root template "A" but on a fragment being inserted
* into "A" from another template called "B", this will return "B" as a
* name, and B's mode as template mode.
*/
${#execInfo.templateName}
${#execInfo.templateMode}
/*
* Return the name and mode of the 'root' template. This means the template
* that the template engine was originally asked to process. So if this
* piece of code is not in the root template "A" but on a fragment being
* inserted into "A" from another template called "B", this will still
* return "A" and A's template mode.
*/
${#execInfo.processedTemplateName}
${#execInfo.processedTemplateMode}
/*
* Return the stacks (actually, List<String> or List<TemplateMode>) of
* templates being processed. The first element will be the
* 'processedTemplate' (the root one), the last one will be the 'leaf'
* template, and in the middle all the fragments inserted in nested
* manner to reach the leaf from the root will appear.
*/
${#execInfo.templateNames}
${#execInfo.templateModes}
/*
* Return the stack of templates being processed similarly (and in the
* same order) to 'templateNames' and 'templateModes', but returning
* a List<TemplateData>

  • #messages:提供获取外部化消息的方法

/*
* Obtain externalized messages. Can receive a single key, a key plus arguments,
* or an array/list/set of keys (in which case it will return an array/list/set of
* externalized messages).
* If a message is not found, a default message (like '??msgKey??') is returned.
*/
${#messages.msg('msgKey')}
${#messages.msg('msgKey', param1)}
${#messages.msg('msgKey', param1, param2)}
${#messages.msg('msgKey', param1, param2, param3)}
${#messages.msgWithParams('msgKey', new Object[] {param1, param2, param3, param4})}
${#messages.arrayMsg(messageKeyArray)}
${#messages.listMsg(messageKeyList)}
${#messages.setMsg(messageKeySet)}
/*
* Obtain externalized messages or null. Null is returned instead of a default
* message if a message for the specified key is not found.
*/
${#messages.msgOrNull('msgKey')}
${#messages.msgOrNull('msgKey', param1)}
${#messages.msgOrNull('msgKey', param1, param2)}
${#messages.msgOrNull('msgKey', param1, param2, param3)}
${#messages.msgOrNullWithParams('msgKey', new Object[] {param1, param2, param3, param4})}
${#messages.arrayMsgOrNull(messageKeyArray)}
${#messages.listMsgOrNull(messageKeyList)}
${#messages.setMsgOrNull(messageKeySet)}

  • #uris:为执行URI/URL操作(转义/不转义)提供的工具对象

/*
* Escape/Unescape as a URI/URL path
*/
${#uris.escapePath(uri)}
${#uris.escapePath(uri, encoding)}
${#uris.unescapePath(uri)}
${#uris.unescapePath(uri, encoding)}
/*
* Escape/Unescape as a URI/URL path segment (between '/' symbols)
*/
${#uris.escapePathSegment(uri)}
${#uris.escapePathSegment(uri, encoding)}
${#uris.unescapePathSegment(uri)}
${#uris.unescapePathSegment(uri, encoding)}
/*
* Escape/Unescape as a Fragment Identifier (#frag)
*/
${#uris.escapeFragmentId(uri)}
${#uris.escapeFragmentId(uri, encoding)}
${#uris.unescapeFragmentId(uri)}
${#uris.unescapeFragmentId(uri, encoding)}
/*
* Escape/Unescape as a Query Parameter (?var=value)
*/
${#uris.escapeQueryParam(uri)}
${#uris.escapeQueryParam(uri, encoding)}
${#uris.unescapeQueryParam(uri)}
${#uris.unescapeQueryParam(uri, encoding)}

  • #conversions:允许转换服务在模板任意地方执行的工具对象

/*
* Execute the desired conversion of the 'object' value into the
* specified class.
*/
${#conversions.convert(object, 'java.util.TimeZone')}
${#conversions.convert(object, targetClass)}

  • #dates:提供java.util.Date相关的工具对象

/*
* Format date with the standard locale format
* Also works with arrays, lists or sets
*/
${#dates.format(date)}
${#dates.arrayFormat(datesArray)}
${#dates.listFormat(datesList)}
${#dates.setFormat(datesSet)}
/*
* Format date with the ISO8601 format
* Also works with arrays, lists or sets
*/
${#dates.formatISO(date)}
${#dates.arrayFormatISO(datesArray)}
${#dates.listFormatISO(datesList)}
${#dates.setFormatISO(datesSet)}
/*
* Format date with the specified pattern
* Also works with arrays, lists or sets
*/
${#dates.format(date, 'dd/MMM/yyyy HH:mm')}
${#dates.arrayFormat(datesArray, 'dd/MMM/yyyy HH:mm')}
${#dates.listFormat(datesList, 'dd/MMM/yyyy HH:mm')}
${#dates.setFormat(datesSet, 'dd/MMM/yyyy HH:mm')}
/*
* Obtain date properties
* Also works with arrays, lists or sets
*/
${#dates.day(date)}
${#dates.month(date)}
${#dates.monthName(date)}
${#dates.monthNameShort(date)}
${#dates.year(date)}
${#dates.dayOfWeek(date)}
${#dates.dayOfWeekName(date)}
${#dates.dayOfWeekNameShort(date)}
${#dates.hour(date)}
${#dates.minute(date)}
${#dates.second(date)}
${#dates.millisecond(date)}
/*
* Create date (java.util.Date) objects from its components
*/
${#dates.create(year,month,day)}
${#dates.create(year,month,day,hour,minute)}
${#dates.create(year,month,day,hour,minute,second)}
${#dates.create(year,month,day,hour,minute,second,millisecond)}
/*
* Create a date (java.util.Date) object for the current date and time
*/
${#dates.createNow()}
${#dates.createNowForTimeZone()}
/*
* Create a date (java.util.Date) object for the current date (time set to 00:00)
*/
${#dates.createToday()}
${#dates.createTodayForTimeZone()}
/*
* Create a date (java.util.Date) object for the current date (time set to 00:00)
*/
${#dates.createToday()}
${#dates.createTodayForTimeZone()}

  • #calendars:类似#dates

/*
* Format calendar with the standard locale format
* Also works with arrays, lists or sets
*/
${#calendars.format(cal)}
${#calendars.arrayFormat(calArray)}
${#calendars.listFormat(calList)}
${#calendars.setFormat(calSet)}
/*
* Format calendar with the ISO8601 format
* Also works with arrays, lists or sets
*/
${#calendars.formatISO(cal)}
${#calendars.arrayFormatISO(calArray)}
${#calendars.listFormatISO(calList)}
${#calendars.setFormatISO(calSet)}
/*
* Format calendar with the specified pattern
* Also works with arrays, lists or sets
*/
${#calendars.format(cal, 'dd/MMM/yyyy HH:mm')}
${#calendars.arrayFormat(calArray, 'dd/MMM/yyyy HH:mm')}
${#calendars.listFormat(calList, 'dd/MMM/yyyy HH:mm')}
${#calendars.setFormat(calSet, 'dd/MMM/yyyy HH:mm')}
/*
* Obtain calendar properties
* Also works with arrays, lists or sets
*/
${#calendars.day(date)}
//
${#calendars.month(date)}
//
${#calendars.monthName(date)}
//
${#calendars.monthNameShort(date)}
//
${#calendars.year(date)}
//
${#calendars.dayOfWeek(date)}
//
${#calendars.dayOfWeekName(date)}
//
${#calendars.dayOfWeekNameShort(date)} //
${#calendars.hour(date)}
//
${#calendars.minute(date)}
//
${#calendars.second(date)}
//
${#calendars.millisecond(date)}

/*
* Create calendar (java.util.Calendar) objects from its components
*/
${#calendars.create(year,month,day)}
${#calendars.create(year,month,day,hour,minute)}
${#calendars.create(year,month,day,hour,minute,second)}
${#calendars.create(year,month,day,hour,minute,second,millisecond)}
${#calendars.createForTimeZone(year,month,day,timeZone)}
${#calendars.createForTimeZone(year,month,day,hour,minute,timeZone)}
${#calendars.createForTimeZone(year,month,day,hour,minute,second,timeZone)}
${#calendars.createForTimeZone(year,month,day,hour,minute,second,millisecond,timeZone)}
/*
* Create a calendar (java.util.Calendar) object for the current date and time
*/
${#calendars.createNow()}
${#calendars.createNowForTimeZone()}
/*
* Create a calendar (java.util.Calendar) object for the current date (time set to 00:00)
*/
${#calendars.createToday()}
${#calendars.createTodayForTimeZone()}

  • #numbers:数字对象的工具类

/*
* ==========================
* Formatting integer numbers
* ==========================
*/
/*
* Set minimum integer digits.
* Also works with arrays, lists or sets
*/
${#numbers.formatInteger(num,3)}
${#numbers.arrayFormatInteger(numArray,3)}
${#numbers.listFormatInteger(numList,3)}
${#numbers.setFormatInteger(numSet,3)}
/*
* Set minimum integer digits and thousands separator:
* 'POINT', 'COMMA', 'WHITESPACE', 'NONE' or 'DEFAULT' (by locale).
* Also works with arrays, lists or sets
*/
${#numbers.formatInteger(num,3,'POINT')}
${#numbers.arrayFormatInteger(numArray,3,'POINT')}
${#numbers.listFormatInteger(numList,3,'POINT')}
${#numbers.setFormatInteger(numSet,3,'POINT')}
/*
* ==========================
* Formatting decimal numbers
* ==========================
*/
/*
* Set minimum integer digits and (exact) decimal digits.
* Also works with arrays, lists or sets
*/
${#numbers.formatDecimal(num,3,2)}
${#numbers.arrayFormatDecimal(numArray,3,2)}
${#numbers.listFormatDecimal(numList,3,2)}
${#numbers.setFormatDecimal(numSet,3,2)}
/*
* Set minimum integer digits and (exact) decimal digits, and also decimal separator.
* Also works with arrays, lists or sets
*/
${#numbers.formatDecimal(num,3,2,'COMMA')}
${#numbers.arrayFormatDecimal(numArray,3,2,'COMMA')}
${#numbers.listFormatDecimal(numList,3,2,'COMMA')}
${#numbers.setFormatDecimal(numSet,3,2,'COMMA')}
/*
* Set minimum integer digits and (exact) decimal digits, and also thousands and
* decimal separator.
* Also works with arrays, lists or sets
*/
${#numbers.formatDecimal(num,3,'POINT',2,'COMMA')}
${#numbers.arrayFormatDecimal(numArray,3,'POINT',2,'COMMA')}
${#numbers.listFormatDecimal(numList,3,'POINT',2,'COMMA')}
${#numbers.setFormatDecimal(numSet,3,'POINT',2,'COMMA')}
/*
* =====================
* Formatting currencies
* =====================
*/
${#numbers.formatCurrency(num)}
${#numbers.arrayFormatCurrency(numArray)}
${#numbers.listFormatCurrency(numList)}
${#numbers.setFormatCurrency(numSet)}
/*
* ======================
* Formatting percentages
* ======================
*/
${#numbers.formatPercent(num)}
${#numbers.arrayFormatPercent(numArray)}
${#numbers.listFormatPercent(numList)}
${#numbers.setFormatPercent(numSet)}
/*
* Set minimum integer digits and (exact) decimal digits.
*/
${#numbers.formatPercent(num, 3, 2)}
${#numbers.arrayFormatPercent(numArray, 3, 2)}
${#numbers.listFormatPercent(numList, 3, 2)}
${#numbers.setFormatPercent(numSet, 3, 2)}
/*
* ===============
* Utility methods
* ===============
*/
/*
* Create a sequence (array) of integer numbers going
* from x to y
*/
${#numbers.sequence(from,to)}
${#numbers.sequence(from,to,step)}

  • #strings :String对象的工具类

/*
* Null-safe toString()
*/
${#strings.toString(obj)}
// also array*, list* and set*
/*
* Check whether a String is empty (or null). Performs a trim() operation before check
* Also works with arrays, lists or sets
*/
${#strings.isEmpty(name)}
${#strings.arrayIsEmpty(nameArr)}
${#strings.listIsEmpty(nameList)}
${#strings.setIsEmpty(nameSet)}
/*
* Perform an 'isEmpty()' check on a string and return it if false, defaulting to
* another specified string if true.
* Also works with arrays, lists or sets
*/
${#strings.defaultString(text,default)}
${#strings.arrayDefaultString(textArr,default)}
${#strings.listDefaultString(textList,default)}
${#strings.setDefaultString(textSet,default)}
/*
* Check whether a fragment is contained in a String
* Also works with arrays, lists or sets
*/
${#strings.contains(name,'ez')}
${#strings.containsIgnoreCase(name,'ez')}
/*
* Check whether a String starts or ends with a fragment
* Also works with arrays, lists or sets
*/
${#strings.startsWith(name,'Don')}
${#strings.endsWith(name,endingFragment)}
/*
* Substring-related operations
* Also works with arrays, lists or sets
*/
${#strings.indexOf(name,frag)}
${#strings.substring(name,3,5)}
${#strings.substringAfter(name,prefix)}
${#strings.substringBefore(name,suffix)}
${#strings.replace(name,'las','ler')}
/*
* Append and prepend
* Also works with arrays, lists or sets
*/
${#strings.prepend(str,prefix)}
${#strings.append(str,suffix)} // also array*, list* and set*
// also array*, list* and set*
/*
* Change case
* Also works with arrays, lists or sets
*/
${#strings.toUpperCase(name)}
${#strings.toLowerCase(name)} // also array*, list* and set*
// also array*, list* and set*
/*
* Split and join
*/
${#strings.arrayJoin(namesArray,',')}
${#strings.listJoin(namesList,',')}
${#strings.setJoin(namesSet,',')}
${#strings.arraySplit(namesStr,',')}
${#strings.listSplit(namesStr,',')}
${#strings.setSplit(namesStr,',')} // returns String[]
// returns List<String>
// returns Set<String>

  • #objects:对象的工具类

/*
* Return obj if it is not null, and default otherwise
* Also works with arrays, lists or sets
*/
${#objects.nullSafe(obj,default)}
${#objects.arrayNullSafe(objArray,default)}
${#objects.listNullSafe(objList,default)}
${#objects.setNullSafe(objSet,default)}

  • #bools:布尔计算的工具类

/*
* Evaluate a condition in the same way that it would be evaluated in a th:if tag
* (see conditional evaluation chapter afterwards).
* Also works with arrays, lists or sets
*/
${#bools.isTrue(obj)}
${#bools.arrayIsTrue(objArray)}
${#bools.listIsTrue(objList)}
${#bools.setIsTrue(objSet)}
/*
* Evaluate with negation
* Also works with arrays, lists or sets
*/
${#bools.isFalse(cond)}
${#bools.arrayIsFalse(condArray)}
${#bools.listIsFalse(condList)}
${#bools.setIsFalse(condSet)}
/*
* Evaluate and apply AND operator
* Receive an array, a list or a set as parameter
*/
${#bools.arrayAnd(condArray)}
${#bools.listAnd(condList)}
${#bools.setAnd(condSet)}
/*
* Evaluate and apply OR operator
* Receive an array, a list or a set as parameter
*/
${#bools.arrayOr(condArray)}
${#bools.listOr(condList)}
${#bools.setOr(condSet)}

  • #arrays:数组工具类

/*
* Converts to array, trying to infer array component class.
* Note that if resulting array is empty, or if the elements
* of the target object are not all of the same class,
* this method will return Object[].
*/
${#arrays.toArray(object)}
/*
* Convert to arrays of the specified component class.
*/
${#arrays.toStringArray(object)}
${#arrays.toIntegerArray(object)}
${#arrays.toLongArray(object)}
${#arrays.toDoubleArray(object)}
${#arrays.toFloatArray(object)}
${#arrays.toBooleanArray(object)}
/*
* Compute length
*/
${#arrays.length(array)}
/*
* Check whether array is empty
*/
${#arrays.isEmpty(array)}
/*
* Check if element or elements are contained in array
*/
${#arrays.contains(array, element)}
${#arrays.containsAll(array, elements)}

  • #lists:列表的工具类

/*
* Converts to list
*/
${#lists.toList(object)}
/*
* Compute size
*/
${#lists.size(list)}
/*
* Check whether list is empty
*/
${#lists.isEmpty(list)}
/*
* Check if element or elements are contained in list
*/
${#lists.contains(list, element)}
${#lists.containsAll(list, elements)}
/*
* Sort a copy of the given list. The members of the list must implement
* comparable or you must define a comparator.
*/
${#lists.sort(list)}
${#lists.sort(list, comparator)}

  • #sets:集合工具类

/*
* Converts to set
*/
${#sets.toSet(object)}
/*
* Compute size
*/
${#sets.size(set)}
/*
* Check whether set is empty
*/
${#sets.isEmpty(set)}
/*
* Check if element or elements are contained in set
*/
${#sets.contains(set, element)}
${#sets.containsAll(set, elements)}

  • #maps:映射工具类

/*
* Compute size
*/
${#maps.size(map)}
/*
* Check whether map is empty
*/
${#maps.isEmpty(map)}
/*
* Check if key/s or value/s are contained in maps
*/
${#maps.containsKey(map, key)}
${#maps.containsAllKeys(map, keys)}
${#maps.containsValue(map, value)}
${#maps.containsAllValues(map, value)}

  • #aggregates:对数组和集合进行的统计的工具类

/*
* Compute sum. Returns null if array or collection is empty
*/
${#aggregates.sum(array)}
${#aggregates.sum(collection)}
/*
* Compute average. Returns null if array or collection is empty
*/
${#aggregates.avg(array)}
${#aggregates.avg(collection)}

  • #ids:处理id属性的工具类

/*
* Normally used in th:id attributes, for appending a counter to the id attribute value
* so that it remains unique even when involved in an iteration process.
*/
${#ids.seq('someId')}
/*
* Normally used in th:for attributes in <label> tags, so that these labels can refer to Ids
* generated by means if the #ids.seq(...) function.
*
* Depending on whether the <label>

举例:

<p>
Today is: <span th:text="${#calendars.format(today,'dd MMMM yyyy')}">13 May 2011</span>
</p>

HomeController.java

@Controller
public class HomeController{

@RequestMapping(value = {"/index","/home","/"},method = RequestMethod.GET)
public String getHomePage(Model model, Locale locale, HttpServletRequest request) {
model.addAttribute("today",new Date());
return "home";
}
}

14.数据转换和格式化

Thymeleaf为变量表达式({{…}},*{{…}},允许我们使用配置好的转换服务对数据进行转换。基本形式:

<td th:text="${{user.lastAccessDate}}">...</td>

15.给任意属性设置值

很可惜,这种给属性值设置值的方式,在模板中并不常用。这种方式通过th:attr就能够改变标记的属性的值:

<form action="subscribe.html" data-th-attr="action=@{/subscribe}">
<fieldset>
<input type="text" name="email"/>
<input type="submit" value="Subscribe!" data-th-attr="value=#{subscribe.submit}">
</fieldset>
</form>

th:attr用逗号分隔开每个属性:

<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" />

16.给具体属性设置值th:*

如设置标记的value属性:

<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>

再如form标记的action属性:

<form action="subscribe.html" th:action="@{/subscribe}">

还有更多,请参考Thymeleaf官方的相关文档。

17.迭代

17.1.th:each迭代属性

举例

ProductListController.java

@Controller
public class ProductListController {

@RequestMapping(value = {"product/list"},method = RequestMethod.GET)
public String showProducts(Model model, Locale locale, HttpServletRequest request) {

ProductService productService = new ProductService();
List<Product> products = productService.findAll();
model.addAttribute("products",products);
return "product/list";
}
}

product/list.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<link rel="stylesheet" type="text/css" media="all" data-th-href="@{/css/wgrocery.css}" />
<title>Title</title>
</head>
<body>
<h1>Product List</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>In Stock</th>
<th>Comments</th>
</tr>
</thead>
<tbody data-th-remove="all-but-first">
<!--`product : ${products}`意思是为 ${products}中的每一个元素重复这个模板片段,并通过product变量使用当前元素。-->
<tr data-th-each="product : ${products}" data-th-class="${productStat.odd}? 'odd'">
<td data-th-text="${product.name}"></td>
<td data-th-text="${product.price}"></td>
<td data-th-text="${product.inStock} ? #{true} : ${false}"></td>
<td>
<span data-th-text="${#lists.size(product.comments)}">0</span> comment/s
<a href="comments.html" data-th-href="@{/product/comments(prodId=${product.id})}"
data-th-unless="${#lists.isEmpty(product.comments)}">view</a>
</td>
</tr>
</tbody>

</table>
<p>
<a href="../home.html" th:href="@{/home}">Return to home</a>
</p>

</body>
</html>

我们在list.html使用th:each迭代products列表。​​product : ${products}​​意思是为 ${products}中的每一个元素重复这个模板片段,并通过product变量使用当前元素。

  • ${products}:我们称其为被迭代表达式或被迭代变量
  • product:我们称其为迭代变量或中间变量

### 17.2什么对象可以被迭代呢?

  1. 任何实现了java.util.Iterable的对象
  2. 任何实现了java.util.Enumeration的对象
  3. 任何实现了java.util.Iterator的对象
  4. 任何实现了java.util.Map的对象
  5. 任何数组

17.3获取迭代状态

当使用​​th:each​​​时,Thymeleaf提供一些状态变量跟踪迭代的状态。这些状态变量在​​th:each​​属性中定义的,包括以下这一些:

  1. index:当前迭代的索引,从0开始
  2. count:当前迭代的索引,从1开始
  3. size:被迭代变量中元素总数
  4. current:每次迭代的迭代变量
  5. even/odd:检查当前迭代是双数还是单数,返回true或false
  6. first:检查当前迭代是否是第一个
  7. last:检查当前迭代是否是最后一个

17.3.1如何获得这些状态变量呢?

<tr data-th-each="product,dd : ${products}" data-th-class="${dd.odd}? 'odd'">...</tr>

状态变量是定义在th:each属性当中的,跟在迭代变量后,用逗号分开,如上面的dd就是状态变量。事实上,如果我们不显示定义我们的状态变量,Thymeleaf也会帮我们创建一个,它的名称是​​迭代变量+Stat后缀​​,如下所示:

<tr data-th-each="product : ${products}" data-th-class="${productStat.odd}? 'odd'">...</tr>

上面没有显示定义状态变量,所以默认的状态变量是productStat。

18.数据懒加载

当数据需要时,再加载。为了支持懒加载,Thymeleaf提供了懒加载上下文变量,上下文变量实现了ILazyContextVariable接口。在大多数情况下,都直接继承LazyContextVariable的默认实现。举个例子:

ProductListController.java

@Controller
public class ProductListController {

@RequestMapping(value = {"product/list"},method = RequestMethod.GET)
public String showProducts(Model model, boolean show) {

ProductService productService = new ProductService();
// 数据懒加载
model.addAttribute("products", new LazyContextVariable<List<Product>>() {
@Override
protected List<Product> loadValue() {
return productService.findAll();
}
});
model.addAttribute("show",show);
return "product/list";
}
}

list.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<link rel="stylesheet" type="text/css" media="all" data-th-href="@{/css/wgrocery.css}" />
<title>Title</title>
</head>
<body>
<!-- 如果是true,则进行入执行-->
<table data-th-if="${show}">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>In Stock</th>
<th>Comments</th>
</tr>
</thead>
<tbody data-th-remove="all-but-first">
<tr data-th-each="product : ${products}" data-th-class="${productStat.odd}? 'odd'">
<td data-th-text="${product.name}"></td>
<td data-th-text="${product.price}"></td>
<td data-th-text="${product.inStock} ? #{true} : ${false}"></td>
<td>
<span data-th-text="${#lists.size(product.comments)}">0</span> comment/s
<a href="comments.html" data-th-href="@{/product/comments(prodId=${product.id})}"
data-th-unless="${#lists.isEmpty(product.comments)}">view</a>
</td>
</tr>
</tbody>
</table>
</body>
</html>

  • ​​http://localhost:8080/product/list?show=true:加载数据​​
  • ​​http://localhost:8080/product/list?show=false:不加载数据​​

如果Controller里,没有使用如下懒加载:

// 懒加载
model.addAttribute("products", new LazyContextVariable<List<Product>>() {
@Override
protected List<Product> loadValue() {
return productService.findAll();
}
});

那么无论是​​http://localhost:8080/product/list?show=true​​​还是​​http://localhost:8080/product/list?show=false​​都会触发数据加载,区别仅在于有没有显示出来而已。

19.条件属性th:if和th:unless

有时模板里的部分代码,你只想在符合某种条件下时才显示。如上面的产品列表里,如果评论数据不为0时,则显示view按钮:

<a href="comments.html" data-th-href="@{/product/comments(prodId=${product.id})}"
data-th-if="not ${#lists.isEmpty(product.comments)}">view</a>

th:if属性的表达式的值为true的情况,遵循以下规则:

  • 如果值为非null,表达式的值就是true
  1. 如果值是布尔类型,并且值是true
  2. 如果值是数字类型,并且值是非0的情况
  3. 如果值是字符类型,并且值是非0的情况
  4. 如果值是字符串类型,并且值不是“false”、“off”、“no”的情况
  5. 如果值不布尔值、数字值、字符值、字符串值的情况
  • 如果值是null,那么th:if的值是false

th:if有一个相反的属性th:unless:

<a href="comments.html" data-th-href="@{/product/comments(prodId=${product.id})}"
data-th-if="not ${#lists.isEmpty(product.comments)}">view</a>

等价于

<a href="comments.html" data-th-href="@{/product/comments(prodId=${product.id})}"
data-th-unless="${#lists.isEmpty(product.comments)}">view</a>

20.switch声明th:switch和th:case

只匹配一个,​​th: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>

21.模板布局

21.1模板片段

将模板中的公共部分抽取出来单独放在一个html中,然后在模板中引用。这种方式有利用我们复用代码、修改代码、维护代码。如将模板网页的header或footer、menus抽取出来,可以这样做使用th:fragment属性定义片段:

  • 定义片段​​th:fragment​​片段也是一个完整的html。下面我们在footer.html里定义了一个片段copy

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div data-th-fragment="copy">
$copy; 2020 The W Grocery Store
</div>
</body>
</html>

  • 在模板中包含这些片段
    在模板中使用​​​th:insert​​​和​​th:replace​​​(​​th:include​​​也可以,但在Thymeleaf3.0开始不再推荐使用)
    我们在模板list.html使用copy片段:

<body>
...
<div th:insert="~{footer :: copy}"></div>
</body>

等价于

<body>
...
<div th:replace="~{footer :: copy}"></div>
</body>

等价于

<body>
...
<div th:insert="footer :: copy"></div>
</body>

等价于

<body>
...
<div th:replace="footer :: copy"></div>
</body>

​th:insert​​​、​​th:replace​​​、​​th:include​​的区别:

有以下这样一个片段:

<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</footer>

分别用三种方式引入:

<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>

21.2.参数化片段签名

让片段有类似函数的机制。定义参数化片段:

<div th:fragment="frag (onevar,twovar)">
<p th:text="${onevar} + ' - ' + ${twovar}">...</p>
</div>

使用​​th:insert​​​或​​th:replace​​调用参数化片段

<div th:replace="::frag (${value1},${value2})">...</div>
<div th:replace="::frag (onevar=${value1},twovar=${value2})">...</div>

上面最后一种方式可以不理参数的顺序,如:

<div th:replace="::frag (twovar=${value2},onevar=${value1})">...</div>

如果片段定义是没有参数,如下面这样的:

<div th:fragment="frag">
<p th:text="${onevar} + ' - ' + ${twovar}">...</p>
</div>

那么我们只能用第二种方式来引用,即:

<div th:replace="::frag (onevar=${value1},twovar=${value2})">

等价于​​th:replace​​​ 和 ​​th:with​​的组合

<div th:replace="::frag" th:with="onevar=${value1},twovar=${value2}">

21.3模板内的断言

断言属性​​th:assert​​可以指定一个以逗号分隔的表达式列表,列表中的表达式必须都为true,否则就会报异常:

<div th:assert="${onevar},(${twovar} != 43)">...</div>

这个属性可以很方便验证片段签名的参数:

<header th:fragment="contentheader(title)" th:assert="${!#strings.isEmpty(title)}">...</header>

21.4灵活的布局

我们可以指定参数,这个参数是某个片段来达到灵活布局的目的:

<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>

引用以上片段:

<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 th:replace="base :: common_header(~{::title},~{})">
<title>Awesome - Main</title>
</head>

如果是想保留片段中的默认值,可以指定它对相应的参数不操作,如:

<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>

那么片段中的title标记的默认值就会被保留。

21.5有条件插入片段

根据不同角色选用不同的片段:

<!--匹配上,则用片段,否则插入空片段-->
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : ~{}">...</div>

<!--匹配上,则用片段,否则不改变模板标记-->
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : ~{}">...</div>

21.6移除模板片段

使用​​th:remove​​属性移除片段,这个属性有5种行为方式:

  1. all:移除包含标签及其孩子标签
  2. body:不移除包含标签,只移除其孩子标签
  3. tag:只移除包含标签,不移除其孩子标签
  4. all-but-first:移除包含标签的所有孩子标签,除了第一个之外
  5. none:什么也不做

移除也可以带条件的:

<a href="/something" th:remove="${condition}? tag : none">Link text not to be removed</a>

由上可知​​th:remove​​属性会视null为none:

<a href="/something" th:remove="${condition}? tag">Link text not to be removed</a>

21.7片段的继承

定义一个片段layout,有两个参数title和content:

<!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>

html标签会被替换,title和content也会被各自替换。

22.本地变量

一般可以在​​th:each​​​迭代时定义本地变量,其实可以用​​th:with​​定义本地变量:

<div th:with="firstPer=${persons[0]}">
<p>
The name of the first person is <span th:text="${firstPer.name}">Julius Caesar</span>.
</p>
</div>

也可以同时定义多个本地变量:

<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>

th:with属性允许重复利用本地变量:

<div th:with="company=${user.company + ' Co.'},account=${accounts[company]}">...</div>

23.th:block块属性

Thymeleaf中唯一一个作为元素处理器的属性th:block。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>

本地变量user可以在th:block包围的范围内使用。

24.内联表达式

如果有一个标签是这样写的:

<p>Hello, <span th:text="${session.user.name}">Sebastian</span>!</p>

它完成span标签来辅助完成,其实内联表达式就可以完美达到这一点:

<p>Hello, [[${session.user.name}]]!</p>

等价于

<p>Hello, [(${session.user.name})]!</p>

​[[...]]​​​或​​[(...)]​​ 这两种内联表达式可以让我们直接将表达式写到html中去。

​[[...]]​​​对应​​th:text​​​,​​[(...)]​​​对应​​th:utext​

25.禁用内联表达式

<p th:inline="none">A double array looks like this: [[1, 2, 3], [4, 5]]!</p>

26.文本内联

需要显示启用。与上面看到的内联表达式差不多,但更强大。

<div th:inline="text">
...
</div>

27.javascript内联

需要显示启用。可以更好的对标签<script>进行集成。

<script th:inline="javascript">
...
var username = [[${session.user.name}]];
...
</script>

28.CSS内联

需要显示启用。可以更好的对标签<style>进行集成。

<style th:inline="css">.[[${classname}]] {
text-align: [[${align}]];
}</style>

上面就是Thymeleaf3.0的大概教程了。学习完,也基本掌握Thymeleaf3.0了。


举报

相关推荐

0 条评论