Using Thymeleaf 03:迭代、条件判断、模板继承

Using Thymeleaf 03:迭代、条件判断、模板继承

一鼓作气看完了Thymeleaf的主要内容,用起来更加得心应手了。

接下来也是模板渲染中非常常用的部分:迭代展示数据。之后是条件表达式和模板继承。

迭代(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之外,还有如下对象都可以被迭代展开并渲染:

  1. 实现了java.util.Iterable接口的对象
  2. 实现了java.util.Enumeration接口的对象
  3. 实现了java.util.Iterator接口的对象
  4. 实现了java.util.Map接口的对象,迭代出的每一个元素是java.util.Map.Entry对象。
  5. 数组

控制迭代序号

Thymeleafth:each的表达式中,可以在迭代变量名后再写一个变量,这个变量实际上是一个对象,具有如下属性:

  1. index,从0开始的索引
  2. count,从1开始的索引
  3. size,集合里元素的数量
  4. current,就是当前元素
  5. even/odd,是一个布尔值,表示当前奇偶
  6. first,布尔值,表示当前元素是不是第一个元素
  7. 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中的判断会生效,将奇数行的文字颜色设置为Bootstraptext-danger样式。

迭代的这个功能,要比Django模板要强大一些,尤其是哪个迭代控制对象,省了很多额外的判断。

条件判断

在模板中非常常用的还有条件判断。这里的条件判断值的不是条件表达式,而是偏重与属性。

th:ifth:unless

这两个标签是互为反义的。
th:if会判断其表达式,如果为true,则th:if所在的标签会被正常渲染,如果不是,则这个标签及其内部都不会被渲染。
th:unless的语义与th:if正好相反。

要注意表达式什么情况下会被判别为true

  1. 如果表达式是null,会被当成false
  2. 如果表达式是一个布尔值,则按照布尔值来决定true or false
  3. 如果是数字,非零值是true
  4. 如果是字符串,不能是falseoff或者no
  5. 不是字符串,布尔值,字符或者数字以外的对象,都判定为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:replaceth:includeth: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用于控制当前的元素是否需要渲染,其属性有如下几个选择:

  1. all,表示这个标签及内部元素全部都不渲染
  2. body,所在标签不移除,内部元素全部不渲染
  3. tag,移除所在标签,保留内部元素正常渲染
  4. all-but-first,把所在标签内部的所有子元素,除了第一个以外都移除,然后渲染所在标签及其中第一个子元素
  5. 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,然后把本地的titlecontent部分传进去,就可以实现整个模板的继承:

<!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核心功能,之后使用起来会更加得心应手了。

LICENSED UNDER CC BY-NC-SA 4.0
Comment