Demo下载,可以在这个demo上进行练习。
1.概述
SpringBoot支持的模板引擎:
- Thymeleaf(SpringBoot推荐)
- FreeMarker
- Velocity
- Groovy
- JSP(以前开发Java Web时用的,现在不推荐)
前后端分离开发虽然已普遍存在,Thymeleaf也可以很好的进行前后台的协作开发。
使用Thymeleaf 三大理由:
- 简洁漂亮 容易理解
- 完美支持HTML5 使用浏览器直接打开页面
- 不新增标签 只需增强属性
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>
注意:
- @{…}用在
th:href
上,将会替换掉<a>标签的href属性值 - 可以给表达式给URL添加参数,如
orderId=${o.id}
,如果有多参数,可以使用逗号隔开@{/order/process(execId=${execId},execType='FAST')}
- 变量模板也允许应用在URL上,如
@{/order/{orderId}/details(orderId=${orderId})}
- 以/开头 (如: /order/details )的相对URL,将自动以应用程序上下文名称作为前缀。
- 如果cookie没有启用或未知,
;jsessionid=...
后缀可能会添加到相对URL,因此会话会被保留。这叫URL重写。Thymeleaf允许插入自己的URL重写,通过Servlet APIresponse.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标记的组成部分,不允许出现在属性部分,因此用> <
<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什么对象可以被迭代呢?
- 任何实现了java.util.Iterable的对象
- 任何实现了java.util.Enumeration的对象
- 任何实现了java.util.Iterator的对象
- 任何实现了java.util.Map的对象
- 任何数组
17.3获取迭代状态
当使用th:each
时,Thymeleaf提供一些状态变量跟踪迭代的状态。这些状态变量在th:each
属性中定义的,包括以下这一些:
- index:当前迭代的索引,从0开始
- count:当前迭代的索引,从1开始
- size:被迭代变量中元素总数
- current:每次迭代的迭代变量
- even/odd:检查当前迭代是双数还是单数,返回true或false
- first:检查当前迭代是否是第一个
- 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
- 如果值是布尔类型,并且值是true
- 如果值是数字类型,并且值是非0的情况
- 如果值是字符类型,并且值是非0的情况
- 如果值是字符串类型,并且值不是“false”、“off”、“no”的情况
- 如果值不布尔值、数字值、字符值、字符串值的情况
- 如果值是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种行为方式:
- all:移除包含标签及其孩子标签
- body:不移除包含标签,只移除其孩子标签
- tag:只移除包含标签,不移除其孩子标签
- all-but-first:移除包含标签的所有孩子标签,除了第一个之外
- 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了。