一、概述
thymeleaf是服务器端模板引擎,能够处理HTML、XML、JavaScript、CSS、文本等。thymeleaf 在有网络和无网络的环境下皆可运行,即它可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。这是由于它支持 html 原型,然后在 html 标签里增加额外的属性来达到模板+数据的展示方式。浏览器解释 html 时会忽略未定义的标签属性,所以 thymeleaf 的模板可以静态地运行;当有数据返回到页面时,Thymeleaf 标签会动态地替换掉静态内容,使页面动态显示。
thymeleaf 提供spring标准方言和一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。
二、thymeleaf工程配置
1.spring配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2.thymeleaf使用详细配置
#配置到properties文件中
#thymeleaf start
#spring.thymeleaf.mode的默认值是HTML5,其实是一个很严格的检查,改为LEGACYHTML5可以得到一个可能更友好亲切的格式要求
spring.thymeleaf.mode=LEGACYHTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
#开发时关闭缓存,不然没法看到实时页面
spring.thymeleaf.cache=false
#thymeleaf end
注:LEGACYHTML5需要搭配一个额外的库NekoHTML才可用,需在pom.xml文件中添加该依赖,如下:
<dependency> <groupId>net.sourceforge.nekohtml</groupId> <artifactId>nekohtml</artifactId> <version>1.9.22</version> </dependency>
三、thymeleaf 示例
@Controller
public class HelloWorldController {
private static final Logger log = LoggerFactory.getLogger(HelloController.class);
@GetMapping(value = "/helloworld")
public String hello(Model model) {
String name = "thymeleaf";
model.addAttribute("name", name);
return "helloworld";
}
}
helloworld.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>hello</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<!--/*@thymesVar id="name" type="java.lang.String"*/-->
<p th:text="${name} + ',HelloWorld,Welcome!'">静态页面文本显示</p>
</body>
</html>
## 四、基础语法
1.使html支持thymeleaf
<html xmlns:th="http://www.thymeleaf.org">
2.获取变量值${}
<p th:text="${name} + ',HelloWorld,Welcome!'">静态页面文本显示</p>
3.选择变量表达式*{…}
<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>
<p> 里面的原有的值是为给前端开发时做展示用的,这样在无网络的情况下也能运行。即完全可以前端先写出页面,模拟数据展现效果,后端人员再拿此模板修改即可。
4.链接表达式: @{…}
用来配合link、src、href使用的语法,类似的标签有:th:href和th:src
<!-- http://localhost:8080/demo/profile?userId=xxx-->
<a href="userProfile.html" th:href="@{http://localhost:8080/demo/profile(userId=${user.id})}">view</a>
<!--/login?username=xxx -->
<a href="userProfile.html" th:href="@{/demo/profile(userId=${user.id})}">view</a>
5.消息表达式#{}
通常与th:text属性一起使用,指明声明了th:text标签的文本是#{}中的key多对应的value,而标签内的文本将不显示
<!--
/WEB-INF/templates/demo.properties
home.welcome = this message is from demo.properties
-->
<!-- /WEB-INF/templates/demo.html -->
<p th:text = "#{demo.welcome}">welcome<p>
6.工具对象表达式 #maps
常用于日期、集合、数组对象的访问。这些工具对象就是java对象,可以访问对应java对象的方法来进行各种操作。
<div th:if="${#maps.size(studentVO.students[__${rowStat.index}__].score)!=0}">
<label>${score.key}:</label><input type="text" th:value="${score.value}">
</div>
其他工具对象表达式还有:#dates、#calendars、#numbers、#strings、#objects、#bools、#arrays、#lists、#sets
/*
* 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')}
/*
* Create a date (java.util.Date) object for the current date and time
*/
${#dates.createNow()}
/*
* Create a date (java.util.Date) object for the current date (time set to 00:00)
*/
${#dates.createToday()}
/*
* 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)} // also arrayDay(...), listDay(...), etc.
${#calendars.month(date)} // also arrayMonth(...), listMonth(...), etc.
${#calendars.monthName(date)} // also arrayMonthName(...), listMonthName(...), etc.
${#calendars.monthNameShort(date)} // also arrayMonthNameShort(...), listMonthNameShort(...), etc.
${#calendars.year(date)} // also arrayYear(...), listYear(...), etc.
${#calendars.dayOfWeek(date)} // also arrayDayOfWeek(...), listDayOfWeek(...), etc.
${#calendars.dayOfWeekName(date)} // also arrayDayOfWeekName(...), listDayOfWeekName(...), etc.
${#calendars.dayOfWeekNameShort(date)} // also arrayDayOfWeekNameShort(...), listDayOfWeekNameShort(...), etc.
${#calendars.hour(date)} // also arrayHour(...), listHour(...), etc.
${#calendars.minute(date)} // also arrayMinute(...), listMinute(...), etc.
${#calendars.second(date)} // also arraySecond(...), listSecond(...), etc.
${#calendars.millisecond(date)} // also arrayMillisecond(...), listMillisecond(...), etc.
/*
* 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()}
/*
* ==========================
* 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')}
/*
* ==========================
* Utility methods
* ==========================
*/
/*
* Create a sequence (array) of integer numbers going
* from x to y
*/
${#numbers.sequence(from,to)}
${#numbers.sequence(from,to,step)}
/*
* 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)}
/*
* 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)}
/*
* 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)}
/*
* 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)}
/*
* 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)}
/*
* Check whether a String starts or ends with a fragment
* Also works with arrays, lists or sets
*/
${#strings.startsWith(name,'Don')} // also array*, list* and set*
${#strings.endsWith(name,endingFragment)} // also array*, list* and set*
/*
* Compute length
* Also works with arrays, lists or sets
*/
${#strings.length(str)}
/*
* Null-safe comparison and concatenation
*/
${#strings.equals(str)}
${#strings.equalsIgnoreCase(str)}
${#strings.concat(str)}
${#strings.concatReplaceNulls(str)}
/*
* Random
*/
${#strings.randomAlphanumeric(count)}
7.文本替换
<span th:text="'Welcome to our application, ' + ${user.name} + '!'">
<!-- 等同于下面的表达式;下面的表达式只能包含表达式变量,而不能有条件判断等!-->
<span th:text="|Welcome to our application, ${user.name}!|">
8.运算符
- 数学运算
二元操作:+, - , * , / , %
一元操作: - (负) - 逻辑运算
一元 : and or
二元 : !,not - 比较运算(为避免转义尴尬,可以使用括号中的英文进行比较运算!)
比较:> , < , >= , <= ( gt , lt , ge , le )
等于:== , != ( eq , ne ) - 条件运算
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
(value) ?: (defaultvalue)
9.条件
th:if、th:unless(th:unless于th:if恰好相反,只有表达式中的条件不成立,才会显示其内容)、th:switch
<a th:href="@{/login}" th:if=${session.user == null}>Login</a>
<!-- 等同于下面的表达式 -->
<a th:href="@{/login}" th:unless=${session.user != null}>Login</a>
<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>
10.循环th:each
语法:th:each=“obj,iterStat:${objList}”
迭代对象可以是java.util.List,java.util.Map,数组等;
iterStat称作状态变量,属性有:
index:当前迭代对象的index(从0开始计算)
count: 当前迭代对象的index(从1开始计算)
size:被迭代对象的大小
current:当前迭代变量
even/odd:布尔值,当前循环是否是偶数/奇数(从0开始计算)
first:布尔值,当前循环是否是第一个
last:布尔值,当前循环是否是最后一个
<ol>
<li>List循环:
<table border="1">
<tr>
<th>用户名</th>
<th>邮箱</th>
<th>管理员</th>
<th>状态变量:index</th>
<th>状态变量:count</th>
<th>状态变量:size</th>
<th>状态变量:current.userName</th>
<th>状态变量:even</th>
<th>状态变量:odd</th>
<th>状态变量:first</th>
<th>状态变量:last</th>
</tr>
<tr th:each="user,userStat : ${list}">
<td th:text="${user.userName}">Onions</td>
<td th:text="${user.email}">test@test.com.cn</td>
<td th:text="${user.isAdmin}">yes</td>
<th th:text="${userStat.index}">当前迭代对象的index(从0开始计算)</th>
<th th:text="${userStat.count}"> 当前迭代对象的index(从1开始计算)</th>
<th th:text="${userStat.size}">被迭代对象的大小</th>
<th th:text="${userStat.current.userName}">当前迭代变量</th>
<th th:text="${userStat.even}">状态变量:even****</th>
<th th:text="${userStat.odd}">布尔值,当前循环是否是偶数/奇数(从0开始计算)</th>
<th th:text="${userStat.first}">布尔值,当前循环是否是第一个</th>
<th th:text="${userStat.last}">布尔值,当前循环是否是最后一个</th>
</tr>
</table>
</li>
<li>Map循环:
<div th:each="mapS:${map}">
<div th:text="${mapS}"></div>
</div>
</li>
<li>数组循环:
<div th:each="arrayS:${arrays}">
<div th:text="${arrayS}"></div>
</div>
</li>
</ol>
五、常用标签
- th:action
定义后台控制器路径,类似标签的action属性
<form id="login-form" th:action="@{/login}">...</form>
- th:each
对象遍历,功能类似jstl中的<c:forEach>标签
public class StudentVO{
private List<Student> students;
}
public class Student implements Serializable{
private String firstName;
private String lastName;
}
@RequestMapping(value = "/addStudent", method = RequestMethod.POST)
public String addStudent(@ModelAttribute(value = "studentVO")
StudentVO studentVO,ModelMap model) {...}
<form id="login-form" th:action="@{/addStudent}" th:object="${studentVO}" method="POST">
<div class="student" th:each="stus,rowStat:${studentVO.students}">
<input type="text" class="firstName" value="" th:field="*{students[__${rowStat.index}__].firstName}"></input>
<input type="text" class="lastName" value="" th:field="*{students[__${rowStat.index}__].lastName}"></input>
</div>
</form>
- th:field
常用于表单字段绑定。通常与th:object一起使用。 属性绑定、集合绑定
public class LoginBean implements Serializable{...
private String username;
private List<User> user;
}
public class User implements Serializable{...
private String username;;
}
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(@ModelAttribute(value = "loginBean") LoginBean loginBean,ModelMap model) {..}
<form id="login-form" th:action="@{/login}" th:object="${loginBean}">...
<input type="text" value="" th:field="*{username}"></input>
<input type="text" value="" th:field="*{user[0].username}"></input>
</form>
- th:href
定义超链接,类似标签的href 属性。value形式为@{/logout}
<a th:href="@{/logout}" class="signOut"></a>
- th:id
div id声明,类似html标签中的id属性
<div class="student" th:id = "student+(${rowStat.index}+1)"></div>
- th:if
条件判断
<div th:if="${rowStat.index} == 0">... 该div显示 ...</div>
-
th:include
见th:fragment,如下 -
th:replace
见th:fragment,如下 -
th:fragment
声明定义该属性的div为模板片段,常用与头文件、页尾文件的引入。常与th:include,th:replace一起使用
<!-- 声明模板片段/WEBINF/templates/footer. html -->
<div th: fragment=" copy" >
© 2011 The Good Thymes Virtual Grocery
</div>
<!-- 引入模板片段 -->
<div th: include=" /templates/footer : : copy" ></div>
<div th: replace=" /templates/footer : : copy" ></div>
- th:object
用于表单数据对象绑定,将表单绑定到后台controller的一个JavaBean参数。常与th:field一起使用进行表单数据绑定
public class LoginBean implements Serializable{...}
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(@ModelAttribute(value = "loginBean") LoginBean loginBean,ModelMap model) {...}
<form id="login-form" th:action="@{/login}" th:object="${loginBean}">...</form>
- th:src
用于外部资源引入,类似于
<script th:src="@{/resources/js/jquery/jquery.json-2.4.min.js}"
- th:text
文本显示
<td class="text" th:text="${username}" ></td>
- th:value
用于标签复制,类似标签的value属性
<option th:value="Adult">Adult</option>
<input id="msg" type="hidden" th:value="${msg}" />