文章目录
1. 引入
Thymeleaf 是一个跟 Velocity、FreeMarker 类似的模板引擎,相比于其他的模版引擎,它具有如下的优势:
- Thymeleaf 在有网络和无网络的环境下皆可运行,即它可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。这是由于它支持 html 原型,然后在 html 标签里增加额外的属性来达到模板+数据的展示方式。浏览器解释 html 时会忽略未定义的标签属性,所以 Thymeleaf 的模板可以静态地运行;当有数据返回到页面时,Thymeleaf 标签会动态地替换掉静态内容,使页面动态显示
- Thymeleaf 开箱即用的特性。它提供标准和 Spring 标准两种方言,可以直接套用模板实现 JSTL、 OGNL表达式效果,避免每天套模板、改 Jstl、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言
- Thymeleaf 提供 Spring 标准方言和一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能
2. 表达式
Themleaf中标准的表达式可以分为四类:
- 变量表达式
- 选择表达式
- 文字国际化表达式
- URL表达式
2.1 变量表达式
使用前首先导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>
变量表达式即 OGNL 表达式或 Spring EL 表达式,如果后端通过Model传过来一个count属性值,那么在前端页面使用th:text=${count}就可以获取到相应的值。此时count属性存在于上下文(context)变量容器(map)中,通过指定的键来获取对应的值。
@Controller
public class IndexController {
@GetMapping("/hello")
public String hello(Model model){
model.addAttribute("count", 10);
return "index";
}
}
<!DOCTYPE html>
<!--导入Thymleaf的命名空间-->
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2 th:text="${count}">20</h2>
</body>
</html>
当通过localhost:8080/hello访问时,此时通过表达式得到count的值10来代替静态的20 。因此,页面对应h1的区域输出为10。
ID Name
10 Forlogen
2.2 选择表达式
变量表达式使用的是一个上下文变量容器,而选择表达式使用的是一个预先选择的对象,如果理解呢?假设此时程序定义了一个实体类Person:
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ToString
public class Person {
@Getter
@Setter
private Integer id;
@Getter
@Setter
private String name;
}
在controller中想要往前端页面传递一个Person对象:
@GetMapping("/person")
public String person(Model model){
model.addAttribute("person", Person.builder().id(10).name("Forlogen").build());
return "index";
}
如果使用选择表达式,前端通过Thymleaf获取传过来的person中属性值的方式为:
<table th:object="${person}">
<tr>
<th>ID</th>
<th>Name</th>
</tr>
<tr>
<td th:text="*{id}">20</td>
<td th:text="*{name}">Hello</td>
</tr>
</table>
因为选择表达式使用的是一个预先选择的对象,即上面通过th:object获取的person对象。因此,下面获取person的属性值只需要使用*{属性}的方式。如果使用的是变量表达式,那么需使用${persion.属性}的方式:
<table>
<tr>
<th>ID</th>
<th>Name</th>
</tr>
<tr>
<td th:text="${person.id}">20</td>
<td th:text="${person.name}">Hello</td>
</tr>
</table>
2.3 文字国际化表达式
文字国际化表达式可以从一个外部文件中获取区域文件信息,这里使用的同样是key-value的键值对形式。例如,此时程序中需使用国际化功能,首先需要在Spring Boot的全局配置文件中进行国际化文件的配置:
spring.messages.basename=message
spring.messages.basename的默认值就是message,如果国际化配置文件名为message.properties可以选择省略上述的配置。然后在message.properties中添加国际化属性,这里只是简单的添加一下属性:
index.address=China
index.language=Chinese
当在前端想要获取上面设置的属性值时,可以在HTML页面中使用#{属性}进行获取:
<table>
<tr>
<th th:text="#{index.address}"></th>
<th th:text="#{index.language}"></th>
</tr>
</table>
执行访问,此时页面输出
China Chinese
2.4 URL表达式
URL表达式将一个有用的上下文或会话信息添加在URL中,前端页面中使用@{url}的方式使用。常配合th:action来进行动作的执行操作,例如
<form th:action="@{/createOrder}">
或者通过th:href进行页面的跳转:
<h2 href="index.html" th:href="@{/index}"></h2>
3. 常用th标签
| 关键字 | 功能介绍 | 案例 |
|---|---|---|
| th:id | 替换id | <input th:id="'xxx' + ${collect.id}"/> |
| th:text | 文本替换 | <p th:text="${collect.description}">description</p> |
| th:utext | 支持html的文本替换 | <p th:utext="${htmlcontent}">conten</p>,此时htmlcontent为传递的HTML格式的文本 |
| th:object | 替换对象 | <div th:object="${session.user}"> |
| th:value | 属性赋值 | <input th:value="${user.name}" /> |
| th:with | 变量赋值运算 | <div th:with="isEven=${prodStat.count}%2==0"></div> |
| th:style | 设置样式 | th:style="'display:' + @{(${sitrue} ? 'none' : 'inline-block')} + ''" |
| th:onclick | 点击事件 | th:onclick="'getCollect()'" |
| th:each | 属性赋值 | tr th:each="user,userStat:${users}"> |
| th:if | 判断条件 | <a th:if="${userId == collect.userId}" > |
| th:unless | 和th:if判断相反 | <a th:href="@{/login}" th:unless=${session.user != null}>Login</a> |
| th:href | 链接地址 | <a th:href="@{/login}" th:unless=${session.user != null}>Login</a> /> |
| th:switch | 多路选择 配合th:case 使用 | <div th:switch="${user.role}"> |
| th:case | th:switch的一个分支 | <p th:case="'admin'">User is an administrator</p> |
| th:fragment | 布局标签,定义一个代码片段,方便其它地方引用 | <div th:fragment="alert">,常用于局部内容刷新 |
| th:include | 布局标签,替换内容到引入的文件 | <head th:include="layout :: htmlhead" th:with="title='xx'"></head> /> |
| th:replace | 布局标签,替换整个标签到引入的文件 | <div th:replace="fragments/header :: title"></div> |
| th:selected | selected选择框 选中 | th:selected="(${xxx.id} == ${configObj.dd})" |
| th:src | 图片类地址引入 | <img class="img-responsive" alt="App Logo" th:src="@{/img/logo.png}" /> |
| th:inline | 定义js脚本可以使用变量 | <script type="text/javascript" th:inline="javascript"> |
| th:action | 表单提交的地址 | <form action="subscribe.html" th:action="@{/subscribe}"> |
| th:remove | 删除某个属性 | <tr th:remove="all"> 1.all:删除包含标签和所有的孩子。2.body:不包含标记删除,但删除其所有的孩子。3.tag:包含标记的删除,但不删除它的孩子。4.all-but-first:删除所有包含标签的孩子,除了第一个。5.none:什么也不做。这个值是有用的动态评估。 |
| th:attr | 设置标签属性,多个属性可以用逗号分隔 | 比如th:attr="src=@{/image/aa.jpg},title=#{logo}",此标签不太优雅,一般用的比较少。 |
3.1 字符串
例如使用th:text不仅可以简单的进行文本的替换,还可以进行字符串的拼接:
<span>
<p th:text="'person id is:' + ${person.id} + ', person name is: ' + ${person.name}">person desc</p>
<p th:text="|person id is: ${person.id}, person name is: ${person.name}|"></p>
</span>
上面的两种方式可以获得同样的效果
person id is:10, person name is: Forlogen
person id is: 10, person name is: Forlogen
3.2 条件判断
使用th:if和th:unless可以在前端页面中进行条件判断,th:if只有在满足条件时才怎么怎么样,而th:unless正好相反,只有在条件不成立时才怎么怎么样。
例如向前端传递两个person对象,不同之处在于id的设置:
@GetMapping("/person")
public String person(Model model){
model.addAttribute("person", Person.builder().id(10).name("Forlogen").build());
model.addAttribute("person1", Person.builder().id(2).name("Forlogen").build());
return "index";
}
HTML页面中使用th:if和th:unless进行判断。因为,person.id=10 > 5 成立,页面会显示10;而person1.id =1 > 5不成立,因此页面也会显示2。
<h2 th:text="${person.id}" th:if="${person.id > 5}"></h2>
<h3 th:text="${person1.id}" th:unless="${person1.id > 5}"></h3>
3.3 遍历
使用th:each可以进行集合的遍历,例如向前端传递一个Person的list,然后在前端使用th:each来获取list中的值:
@GetMapping("/person/list")
public String list(Model model){
List<Person> persons = new ArrayList<>();
Collections.addAll(persons,
Person.builder().id(1).name("Forlgoen").build(),
Person.builder().id(2).name("kobe").build());
model.addAttribute("persons", persons);
return "index :: personList";
}
前端还使用了th:fragment来进行局部刷新
<table th:fragment="personList" th:each="person, iterStat : ${persons}">
<tr>
<th>ID</th>
<th>Name</th>
</tr>
<tr>
<td th:text="${person.id}">20</td>
<td th:text="${person.name}">Hello</td>
<td th:text="${iterStat}"></td>
</tr>
</table>
此时页面输出为
ID Name
1 Forlgoen {index = 0, count = 1, size = 2, current = Person(id=1, name=Forlgoen)}
ID Name
2 kobe {index = 1, count = 2, size = 2, current = Person(id=2, name=kobe)}
th:each标签中的iterStat为状态变量,属性有:
- index:当前迭代对象的 index(从0开始计算)
- count: 当前迭代对象的 index(从1开始计算)
- size:被迭代对象的大小
- current:当前迭代变量
- even/odd:布尔值,当前循环是否是偶数/奇数(从0开始计算)
- first:布尔值,当前循环是否是第一个
- last:布尔值,当前循环是否是最后一个
3.4 URL
如果是静态页面实现页面的跳转,可以使用a标签:
<a href="index.html"></a>
而使用Thymleaf进行渲染时,需使用th:href:
<a href="index.html" th:href="@{/index}"></a>
如果连接中包含参数,可使用如下的方式:
<a href="#" th:href="@{/person/{id}(id=${person.id})}" th:text="${blog.title}"></a>
此时会使用person.id替换路径中的id。
3.5 内联JS
如果想要在script标签中内敛文本,首先需使用th:inline="javascript"进行激活,然后使用[[…]]格式进行值的获取。
<script th:inline="javascript">
var id = /*[[${person.id}]]*/"10";
var name = /*[[${person.name}]]*/"Forlogen";
</script>
其中使用/**/包裹表示只有在运行时才动态渲染,使用其中获取到的值。
4. 布局
当构建一个网站时,常用的导航栏、底部等常常是相同的,如果在每个页面中都单独的编写这部分的代码,不免显得有点冗余,此时就可以使用th:fragment进行相同内容的抽取,然后使用th:insert和th:replace进行布局的随处引用。
例如此时在_fragments.html中定义head如下所示:
<head th:fragment="head(title)">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title th:replace="${title}">博客详情</title>
</head>
那么在其他的页面的head标签中只需要引入即可:
<head th:replace="_fragments :: head(~{::title})">
<title>首页</title>
</head>
th:insert 和 th:replace 区别:insert 只是加载,replace 是替换。Thymeleaf 3.0 推荐使用 th:insert 替换 2.0 的 th:replace。
5. 内嵌变量
为了模板更加易用,Thymeleaf 还提供了一系列 Utility 对象(内置于 Context 中),可以通过 # 直接访问:
- dates : java.util.Date的功能方法类。
- calendars : 类似#dates,面向java.util.Calendar
- numbers : 格式化数字的功能方法类
- strings : 字符串对象的功能类,contains,startWiths,prepending/appending等等。
- objects: 对objects的功能类操作。
- bools: 对布尔值求值的功能方法。
- arrays:对数组的功能类方法。
- lists: 对lists功能类方法
- sets
- maps
- …
例如:
# 日期格式化
${#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')}
# 获取当前时间
${#dates.createNow()}
# 获取当前是哪一天
${#dates.createToday()}
${#strings.isEmpty(name)}
${#strings.arrayIsEmpty(nameArr)}
${#strings.listIsEmpty(nameList)}
${#strings.setIsEmpty(nameSet)}
${#strings.startsWith(name,'Don')} // also array*, list* and set*
${#strings.endsWith(name,endingFragment)} // also array*, list* and set*
${#strings.length(str)}
${#strings.equals(str)}
${#strings.equalsIgnoreCase(str)}
${#strings.concat(str)}
${#strings.concatReplaceNulls(str)}
${#strings.randomAlphanumeric(count)}