接下来也是模板渲染中非常常用的部分:迭代展示数据。之后是条件表达式和模板继承。
迭代(Iteration
)
迭代在很多模板语言中都会使用到,方便的把一个集合内部包含的所有元素展示出来,是常见的渲染列表、表格和类似元素的方式。
Thymeleaf
的迭代控制标签是th:each
,其语法如下:
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
th:each
的表达式由两部分组成,冒号后边的部分是一个集合,通过取值表达式取到,冒号前边的部分是在迭代中为集合的每一个元素起的临时名称。这个临时名称就标志着在迭代中每一个元素的名称。th:each
所在的标签包裹的部分(包含th:each
所在的标签),就是集合中每一个元素渲染出的结构。使用起来和Django
模板语言的风格很类似。
能够被Thymeleaf
解析并迭代的对象
除了首先能想到的java.util.List
之外,还有如下对象都可以被迭代展开并渲染:
- 实现了
java.util.Iterable
接口的对象 - 实现了
java.util.Enumeration
接口的对象 - 实现了
java.util.Iterator
接口的对象 - 实现了
java.util.Map
接口的对象,迭代出的每一个元素是java.util.Map.Entry
对象。 - 数组
控制迭代序号
Thymeleaf
在th:each
的表达式中,可以在迭代变量名后再写一个变量,这个变量实际上是一个对象,具有如下属性:
index
,从0开始的索引count
,从1开始的索引size
,集合里元素的数量current
,就是当前元素even/odd
,是一个布尔值,表示当前奇偶first
,布尔值,表示当前元素是不是第一个元素last
,布尔值,表示当前元素是不是最后一个元素
看下边例子:
<table class="table">
<thead>
<tr>
<th>#</th>
<th>name</th>
</tr>
</thead>
<tbody>
<tr th:each="name, control:${names}" th:class="${control.odd}? 'text-danger'">
<td th:text="${control.count}"></td>
<td th:text="${name}"></td>
</tr>
</tbody>
</table>
由于th:each
解析的优先级高于th:class
,所以th:class
中的判断会生效,将奇数行的文字颜色设置为Bootstrap
的text-danger
样式。
迭代的这个功能,要比Django
模板要强大一些,尤其是哪个迭代控制对象,省了很多额外的判断。
条件判断
在模板中非常常用的还有条件判断。这里的条件判断值的不是条件表达式,而是偏重与属性。
th:if
与th:unless
这两个标签是互为反义的。
th:if
会判断其表达式,如果为true
,则th:if
所在的标签会被正常渲染,如果不是,则这个标签及其内部都不会被渲染。
th:unless
的语义与th:if
正好相反。
要注意表达式什么情况下会被判别为true
:
- 如果表达式是
null
,会被当成false
- 如果表达式是一个布尔值,则按照布尔值来决定
true or false
- 如果是数字,非零值是
true
- 如果是字符串,不能是
false
,off
或者no
- 不是字符串,布尔值,字符或者数字以外的对象,都判定为
true
这个例子太容易了,就不放了。
th:switch
功能
这个比上边还要灵活一些,不仅仅是二元判断,类似于switch-case
语句:
前边那个例子改一下:
<table class="table">
<thead>
<tr>
<th>#</th>
<th>name</th>
</tr>
</thead>
<tbody>
<tr th:each="name, control:${names}" th:class="${control.odd}? 'text-danger'" th:switch="${name}">
<td th:text="${control.count}"></td>
<td th:case="owl" >owl</td>
<td th:case="cony" >cony</td>
<td th:case="*" >others</td>
</tr>
</tbody>
</table>
在每一行渲染的时候,除了必定渲染出序号,后边渲染内容,就有了三种分支。其中th:case="*"
相当于switch
语句中的default
。
模板继承
在之前重新组织login.html
等几个页面,采用统一的head
标签的时候,已经简单了解过了Thymeleaf
对于fragment
的操作,以及给fragment
传参的操作,th:replace
、th:include
、th:insert
三者的区别等等。现在接着这个内容往下看更加强大的部分。
fragment
传参:参数片段和本地片段
我们定义了一个fragment
如下:
<head th:fragment="common_header(title,links)">
<!-- 每个页面不同的title -->
<title th:replace="${title}">Title</title>
<!-- 公用的样式和js文件 -->
<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>
<!--/* 每个页面不同的部分 */-->
<th:block th:replace="${links}" />
</head>
注意其中的th:block
元素(而不是标签属性)和与之搭配的th:replace
属性,表示用links
参数中的部分,替换掉th:block
这个元素。也就是说参数不仅仅可以传递上下文中的值,还可以传递模板片段,这就非常牛逼了。
实际使用的时候,在另外一个模板中,可以传入当前模板自己的片段,如下:
<head th:replace="/test/fragment::common_header(~{::title},~{::link})">
<link rel="stylesheet" href="conyli.cc">
<link rel="stylesheet" href="archive.conyli.cc">
</head>
这里的~{::title}
表示当前页面head
标签内部的所有title
元素,后边的~{::link}
也是如此,会将两个link
标签都传递给common_header
这个fragment
,替换好其中元素后再渲染到当前页面。这就实现了动态机制。前边不加模板名称的~{::title}
这种引用方式,就是本地片段引用。
空参数和默认值参数
首先是空参数。接着上边的例子,假如一个页面,只需要自定义title,不需要额外的样式,那么只需要把第二个参数表达式中间留空即可:
<head th:replace="base :: common_header(~{::title},~{})">
之后是默认值参数,如果我的页面直接就使用fragment
中的title
显示,很显然不能传空参数,否则fragment
经过渲染后,其中的title
属性就消失了,这个时候就可以传入之前遇到过的“什么也不干”操作符,也就是一个下划线_
,传了之后,就会原样把fragment
中的title
属性给渲染过来,用法如下:
<head th:replace="base :: common_header(_,~{::link})">
th:remove
取消渲染
这个th:remove
用于控制当前的元素是否需要渲染,其属性有如下几个选择:
all
,表示这个标签及内部元素全部都不渲染body
,所在标签不移除,内部元素全部不渲染tag
,移除所在标签,保留内部元素正常渲染all-but-first
,把所在标签内部的所有子元素,除了第一个以外都移除,然后渲染所在标签及其中第一个子元素none
,什么也不干,一般用于动态控制渲染。
一般常用的就是th:remove="all"
。
整体继承
之前我们的fragment
还都是在某个元素上打打闹闹,实际上可以将html
元素也定义为fragment
,配合本地片段引用,就可以实现整个模板的继承。
例子就放一个官网的:
<!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>
上边这个是整个模板,要替换的有两处,一个是title
,一个是content
部分。在另外一个模板里直接使用这个fragment
,然后把本地的title
和content
部分传进去,就可以实现整个模板的继承:
<!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>
学到这里我就发现了,像之前的login.html
等几个页面的排布,实际可以再进行重构,只需要在一个标准的模板上留好各种自定义的空间即可。而且也可以节省控制器给页面传递title
参数的过程。这个我在编写项目剩余的页面的时候,还需要好好思考一下模板的继承体系。
行内渲染
th:text
这种是用属性来渲染,还有一种替代的方式,就是直接写在标签的文字部分,使用两对方括号括起来表达式来渲染,如下:
<p>Hello, [[${session.user.name}]]!</p>
这种方式渲染出来是纯文本的话,可以被浏览器进行正确解析,所以用这个方式来渲染动态的JavsScript代码也是可以的。
一鼓作气看完了Thymeleaf
核心功能,之后使用起来会更加得心应手了。