第二部分、其他(Thymeleaf官方文档翻译)

原文:文档5-13节

2. 设置属性值

2.1 给任意属性赋值

使用th:attr可以给任意标签进行属性赋值,举例如下:

<form action="subscribe.html" th:attr="action=@{/subscribe}">
  <fieldset>
    <input type="text" name="email" />
    <input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
  </fieldset>
</form>

<form>元素的action属性会设置为@{/subscribe}变量的值

<input type=“submit”>元素的value属性会被设置为#{subscribe.submit}变量的值

多个赋值操作用逗号隔开:

<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" />

2.2 给指定的属性赋值

th:*属性可以给特定的属性赋值,通过把*替换为指定的属性名来使用:

<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">

Thymeleaf支持很多HTML5的属性:

th:abbrth:acceptth:accept-charsetth:accesskeyth:actionth:align
th:altth:archiveth:audioth:autocompleteth:axisth:background
th:bgcolorth:borderth:cellpaddingth:cellspacingth:challengeth:charset
th:citeth:classth:classidth:codebaseth:codetypeth:cols
th:colspanth:compactth:contentth:contenteditableth:contextmenuth:data
th:datetimeth:dirth:draggableth:dropzoneth:enctypeth:for
th:formth:formactionth:formenctypeth:formmethodth:formtargetth:fragment
th:frameth:frameborderth:headersth:heightth:highth:href
th:hreflangth:hspaceth:http-equivth:iconth:idth:inline
th:keytypeth:kindth:labelth:langth:listth:longdesc
th:lowth:manifestth:marginheightth:marginwidthth:maxth:maxlength
th:mediath:methodth:minth:nameth:onabortth:onafterprint
th:onbeforeprintth:onbeforeunloadth:onblurth:oncanplayth:oncanplaythroughth:onchange
th:onclickth:oncontextmenuth:ondblclickth:ondragth:ondragendth:ondragenter
th:ondragleaveth:ondragoverth:ondragstartth:ondropth:ondurationchangeth:onemptied
th:onendedth:onerrorth:onfocusth:onformchangeth:onforminputth:onhashchange
th:oninputth:oninvalidth:onkeydownth:onkeypressth:onkeyupth:onload
th:onloadeddatath:onloadedmetadatath:onloadstartth:onmessageth:onmousedownth:onmousemove
th:onmouseoutth:onmouseoverth:onmouseupth:onmousewheelth:onofflineth:ononline
th:onpauseth:onplayth:onplayingth:onpopstateth:onprogressth:onratechange
th:onreadystatechangeth:onredoth:onresetth:onresizeth:onscrollth:onseeked
th:onseekingth:onselectth:onshowth:onstalledth:onstorageth:onsubmit
th:onsuspendth:ontimeupdateth:onundoth:onunloadth:onvolumechangeth:onwaiting
th:optimumth:patternth:placeholderth:posterth:preloadth:radiogroup
th:relth:revth:rowsth:rowspanth:rulesth:sandbox
th:schemeth:scopeth:scrollingth:sizeth:sizesth:span
th:spellcheckth:srcth:srclangth:standbyth:startth:step
th:styleth:summaryth:tabindexth:targetth:titleth:type
th:usemapth:valueth:valuetypeth:vspaceth:widthth:wrap
th:xmlbaseth:xmllangth:xmlspace

2.3 同时给多个属性赋值

  • th:alt-title 会把 alttitle属性设置为相同的值
  • th:lang-xmllang 会把 langxml:lang属性设置为相同的值

下面的两种写法是等效的

<img src="../../images/gtvglogo.png" 
     th:src="@{/images/gtvglogo.png}" th:title="#{logo}" th:alt="#{logo}" />
<img src="../../images/gtvglogo.png" 
     th:src="@{/images/gtvglogo.png}" th:alt-title="#{logo}" />

2.4 添加和插入

如果你想在某个属性值的后面添加一个值,或者在它前面插入一个值,那么可以使用th:attrappendth:attrprepend ,举个例子:

<input type="button" value="Do it!" class="btn" th:attrappend="class=${' ' + cssStyle}" />
如果cssStyle变量等于warning,那么上面的模板最终结果是:
<input type="button" value="Do it!" class="btn warning" />

还有两个专用的语法:th:classappendth:styleappend,举个例子:

<tr class="row" th:classappend="${prodStat.odd}? 'odd'">
<tr style="color:black;" th:styleappend="${customStyle}">

2.5 布尔属性

在HTML和XHTML中都有布尔属性,但是表示方式不一样。如果属性值为true,那么在HTML中该属性值就是true,而在XHTML中该属性值为属性名,举例来说:

<input type="checkbox" name="option2" checked /> <!-- HTML -->
<input type="checkbox" name="option1" checked="checked" /> <!-- XHTML -->

Thymeleaf支持根据条件来设置合适的值,即如果条件为true,那么属性值会根据文档类型来设置,如果条件为false,那么就不设置这个值:

<input type="checkbox" name="active" th:checked="${user.active}" />

下面是支持的布尔属性:

th:asyncth:autofocusth:autoplay
th:checkedth:controlsth:declare
th:defaultth:deferth:disabled
th:formnovalidateth:hiddenth:ismap
th:loopth:multipleth:novalidate
th:nowrapth:openth:pubdate
th:readonlyth:requiredth:reversed
th:scopedth:seamlessth:selected

2.6 设置任意属性和其值

thymeleaf提供了一个默认的属性处理器,它允许我们设置任何属性的值。

<span th:whatever="${user.name}">...</span>

结果:
<span whatever="John Apricot">...</span>

2.7 HTML5友好型写法

支持使用data-{prefix}-{name} 的语法来设置HTML5元素的属性,不需要开发人员使用th:*这样的命名空间的写法。

<table>
    <tr data-th-each="user : ${users}">
        <td data-th-text="${user.login}">...</td>
        <td data-th-text="${user.name}">...</td>
    </tr>
</table>

还有一种语法用于指定自定义标记:{prefix}-{name},它遵循W3C自定义元素规范。举个例子来说 th:block和th-block支持block元素。

第三部分 遍历

3 遍历

3.1 遍历基础

使用 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>

可遍历的对象:
java.util.List、 java.util.Iterable、 java.util.Enumeration、java.util.Iterator、java.util.Map、任何数组、object(Object被看成是只包含自己的list)

3.2 遍历状态

  • 当前遍历索引, 从0开始:index.
  • 当前遍历索引, 从1开始:count .
  • 被遍历对象包含的元素个数:size.
  • 当前遍历元素:current.
  • 表示当前遍历是偶数还是奇数. even/odd 布尔值.
  • 当前遍历元素是否是第一个元素:first 布尔值.
  • 当前遍历元素是否是最后一个元素: last 布尔值.

使用方法:

 <tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
    <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>

状态变量(在这个例子中是iterStat 变量)定义在遍历元素后面,用逗号隔开。

如果你不想显式声明状态变量,那么Thymeleaf会自动为你创建一个被遍历对象名 + Stat后缀的状态变量:

<tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">
  <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的元素下。

4 通过惰性加载技术优化性能

某些时候我们会有这样的需求,就是当真正需要使用数据的时候,我们才从存放数据的地方(如数据库)获取数据。

Thymeleaf提供了数据惰性加载的机制,要求上下文变量需要实现 ILazyContextVariable接口。可以使用Thymeleaf实现的LazyContextVariable 类来实现:

context.setVariable(
     "users",
     new LazyContextVariable<List<User>>() {
         @Override
         protected List<User> loadValue() {
             return databaseRepository.findAllUsers();
         }
     });

然后在模板里我们这样使用:

<ul th:if="${condition}">
  <li th:each="u : ${users}" th:text="${u.name}">user name</li>
</ul>

当${condition}为true的时候Thymeleaf才会去获取真实的数据,否则users数据将不会被加载。


第四部分 条件取值

5 条件取值

5.1 使用th:if 和 th:unless

我们可能希望只有在if条件为真时,才显示代码模板。这时可以使用th:if。来看一下下面的模板:

<tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">
    <td th:text="${prod.name}">Onions</td>
    <td th:text="${prod.price}">2.41</td>
    <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
    <td>
      <span th:text="${#lists.size(prod.comments)}">2</span> comment/s
      <a href="comments.html" 
         th:href="@{/product/comments(prodId=${prod.id})}" 
         th:if="${not #lists.isEmpty(prod.comments)}">view</a>
    </td>
</tr>

上面的HTML代码中<a>元素只有在th:if判断为真时,才会显示。上面模板最终输出的结果如下:

<tr class="odd">
    <td>Italian Tomato</td>
    <td>1.25</td>
    <td>no</td>
    <td>
      <span>2</span> comment/s
      <a href="/gtvg/product/comments?prodId=2">view</a>
    </td>
</tr>
<tr>
    <td>Yellow Bell Pepper</td>
    <td>2.50</td>
    <td>yes</td>
    <td>
      <span>0</span> comment/s
    </td>
</tr>
<tr class="odd">
    <td>Old Cheddar</td>
    <td>18.75</td>
    <td>yes</td>
    <td>
      <span>1</span> comment/s
      <a href="/gtvg/product/comments?prodId=4">view</a>
    </td>
</tr>

那么有哪些条件下th:if判断为真呢?

  • 值不为空,if条件为真:
    • If value is a boolean and is true.
    • If value is a number and is non-zero
    • If value is a character and is non-zero
    • If value is a String and is not “false”, “off” or “no”
    • If value is not a boolean, a number, a character or a String.
  • 值为空,if条件为假

和th:if相对,th:unless也可以用来条件判断。那么上面的代码就可以改成:

<a href="comments.html"
   th:href="@{/comments(prodId=${prod.id})}" 
   th:unless="${#lists.isEmpty(prod.comments)}">view</a>

也就是说,<a>元素会一直显示,除非th:unless条件为真。

5.2 使用th:switch

和其他编程语言类似,Thymeleaf提供了switch条件判断:

<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>

th:case="*"代表默认值


第六部分 模板布局

6. 模板布局

6.1 引用代码片段

6.1.1 定义和引用代码片段

有时候我们想从其他的模板文件引入代码,这时候可以使用th:fragment来实现。

首先创建一个/WEB-INF/templates/footer.html文件,然后文件内容如下:

<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org">

  <body>
  
    <div th:fragment="copy">
      &copy; 2011 The Good Thymes Virtual Grocery
    </div>
  
  </body>
  
</html>

上面的代码定义了一个名为copy的代码片段。在其他HTML文件中,我们可以使用th:insert或者th:replace属性来引用这段代码(也可以使用th:include,但是在Thymeleaf3.0中不推荐使用):

<body>

  ...

  <div th:insert="~{footer :: copy}"></div>
  
</body>

注意到~{footer :: copy}是一个fragment 表达式,代表结果在一个fragment里。fragment 表达式也可以简写:

<div th:insert="footer :: copy"></div>

6.1.2 Fragment语法规范

Fragment表达式有三种书写方式:

  • "~{templatename::selector}" :从名为templatename的模板文件中,引用被selector选择器选中的代码片段。selector选择器的使用和CSS选择器很相似,它的低层使用了AttoParser parsing,详细内容请看这里Appendix C
  • "~{templatename}" 引用模板名为template的全部内容
  • "~{::selector}" or "~{this::selector}"引用同一个模板文件中的代码片段。当selector找不到匹配项时,模板引擎会搜索从模板根目录搜索,直到找到匹配项位置。
Fragment表达式还可以和其他的语法组合,构成功能更加丰富的语句: ```xml
```

代码模板里可以使用任何th:*的属性,当代码模板插入到其他模板文件中时,所有的th:*属性都会被解析。

6.1.3 不用th:fragment引用代码片段

由于强大的 Markup Selectors的支持,我们可以不用th:fragment属性就可以引用代码片段,甚至是从其他没有使用Thymeleaf的应用里引用代码。看下面的例子:

...
<div id="copy-section">
  &copy; 2011 The Good Thymes Virtual Grocery
</div>
...

我们可以通过引用div的id属性来引用这片代码,选择器的写法和CSS选择器一样:

<body>

  ...

  <div th:insert="~{footer :: #copy-section}"></div>
  
</body>

6.1.4 th:insert和th:replace的不同

  • th:insert 插入标有th:insert的元素,作为其子元素.
  • th:replace 替换标有th:replace的元素
  • th:include和th:insert类似, 但是它仅仅是把fragment代码片段的文本内容插入进来.

看下面的例子,比较不同:

首先有一个名为copy的fragment

<footer th:fragment="copy">
  &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>
      &copy; 2011 The Good Thymes Virtual Grocery
    </footer>
  </div>

  <footer>
    &copy; 2011 The Good Thymes Virtual Grocery
  </footer>

  <div>
    &copy; 2011 The Good Thymes Virtual Grocery
  </div>
  
</body>

6.2 参数化fragment签名

为了创建函数化的fragment,可以给th:fragment添加一系列参数:

<div th:fragment="frag (onevar,twovar)">
    <p th:text="${onevar} + ' - ' + ${twovar}">...</p>
</div>

使用的时候可以这样:

<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>

6.2.1 fragment本地变量写法

即使是没有定义参数,也可以网frag里传入变量。如:

<div th:fragment="frag">
    ...
</div>

可以用第二种语法(只能用第二种语法)来传参:

<div th:replace="::frag (onevar=${value1},twovar=${value2})">

这样即使frag后面没有参数签名,frag内部同样可以使用onevar和twovar这两个变量。这种写法和下面的写法是等效的:

<div th:replace="::frag" th:with="onevar=${value1},twovar=${value2}">

6.2.2 th:assert断言

使用th:assert对表达式集合进行断言,表达式集合用逗号隔开。每个表达式的结果必须为真,否则会抛出异常:

<div th:assert="${onevar},(${twovar} != 43)">...</div>

这对于在片段签名中验证参数非常方便:

<header th:fragment="contentheader(title)" th:assert="${!#strings.isEmpty(title)}">...</header>

6.3 灵活布局:不仅仅是fragment插入

由于强大的fragment表达式的支持,我们可以把数字、bean对象等等设置为fragment的参数,即使不写参数签名都可以。

这大大增强了模板的灵活性。举个例子,有这样一个fragment:

<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>

我们在另一个模板里通过th:replace来使用common_header模板:

...
<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>
...

上面common_header(~{::title},~{::link})中,~{::title}表达式选择了当前模板中的<title>元素,~{::link}表达式选中了当前模板中的所有<link>元素,最终结果是:

...
<head>

  <title>Awesome - Main</title>

  <!-- Common styles and scripts -->
  <link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css">
  <link rel="shortcut icon" href="/awe/images/favicon.ico">
  <script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script>

  <link rel="stylesheet" href="/awe/css/bootstrap.min.css">
  <link rel="stylesheet" href="/awe/themes/smoothness/jquery-ui.css">

</head>
...

6.3.1 使用空的fragment

~{}表示空的fragment。比如使用下面的模板:

<head th:replace="base :: common_header(~{::title},~{})">

  <title>Awesome - Main</title>

</head>
...

其最终结果如下:

...
<head>

  <title>Awesome - Main</title>

  <!-- Common styles and scripts -->
  <link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css">
  <link rel="shortcut icon" href="/awe/images/favicon.ico">
  <script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script>

</head>
...

6.3.2 使用no-operation操作符

使用{_}让模板使用默认的代码。如下所示模板一:

...
<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>
...

common_header(_,~{::link})里传入了no-operation操作符。然后看一下common_header模板里的title部分:

<title th:replace="${title}">The awesome application</title>

那么最终的结果是模板一全部被common_header模板替换,而common_header模板的title元素被保留,没有被替换为Awesome - Main。最终结果如下:

...
<head>

  <title>The awesome application</title>

  <!-- Common styles and scripts -->
  <link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css">
  <link rel="shortcut icon" href="/awe/images/favicon.ico">
  <script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script>

  <link rel="stylesheet" href="/awe/css/bootstrap.min.css">
  <link rel="stylesheet" href="/awe/themes/smoothness/jquery-ui.css">

</head>
...

6.3.3 fragment的更高级的条件断言

考虑这样的条件,当用户是管理员的时候我们插入adminhead模板,否则就插入空的内容。那么我们可以这样做:

<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : ~{}">...</div>

同样地,如果用户是管理员我们就插入adminhead模板,否则什么都不做:

...
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : _">
    Welcome [[${user.name}]], click <a th:href="@{/support}">here</a> for help-desk support.
</div>
...

我们还可以判断fragment存不存在:

...
<!-- The body of the <div> will be used if the "common :: salutation" fragment  -->
<!-- does not exist (or is empty).                                              -->
<div th:insert="~{common :: salutation} ?: _">
    Welcome [[${user.name}]], click <a th:href="@{/support}">here</a> for help-desk support.
</div>
...

6.4 移除代码

被th:remove=“*”标记的元素会在Thymeleaf引擎渲染后移除。*的内容如下:

  • all: Remove both the containing tag and all its children.
  • body: Do not remove the containing tag, but remove all its children.
  • tag: Remove the containing tag, but do not remove its children.
  • all-but-first: Remove all children of the containing tag except the first one.
  • none : Do nothing. This value is useful for dynamic evaluation.
th:remove也可以使用条件判断: ```xml Link text not to be removed ```

th:remove把null当做none的同义词:

<a href="/something" th:remove="${condition}? tag">Link text not to be removed</a>

"${condition}? tag"中,条件如果为假,返回值是null,所以上面的例子是合法的。

6.5 布局继承

我们可以写一个名为layoutFile的模板,声明一个名为layout的fragment,然后把这个layoutFile当做页面的原型。

<!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则会被上面的<title>Page Title</title>和<section>元素的内容替换。


第七部分 本地变量和属性优先级

7 本地变量

7.1 定义本地变量

Thymeleaf允许定义本地变量,本地变量的作用域被限制在申明变量的标签内部。先看一个例子:

<tr th:each="prod : ${prods}">
    ...
</tr>

上面的prod就是本地变量,它的作用域如下:

  • 在<tr>标签上,只要是优先级低于th:eachth:* 属性都可以使用prod变量
  • 所有<tr>标签的子元素都可以使用prod变量。
由于涉及到th:*属性的优先级问题,所以在申明本地变量的标签上使用时要小心了。

除了使用th:each,最通用的申明本地变量的属性是th:with

<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:each属性支持在申明变量时使用已经定义的变量:

<div th:with="company=${user.company + ' Co.'},account=${accounts[company]}">...</div>
注意company变量的复用

7.2 使用本地变量

<p>
  Today is: 
  <span th:with="df=#{date.format}" 
        th:text="${#calendars.format(today,df)}">13 February 2011</span>
</p>

上面的例子中定义了本地变量df,在th:text属性中使用了它。注意:由于th:with属性的优先级高于th:text属性,所以可以在同一个<span>里使用。

属性优先级

OrderFeatureAttributes
1Fragment inclusionth:insert th:replace
2Fragment iterationth:each
3Conditional evaluationth:if th:unless th:switch th:case
4Local variable definitionth:object th:with
5General attribute modificationth:attr th:attrprepend th:attrappend
6Specific attribute modificationth:value th:href th:src …
7Text (tag body modification)th:text th:utext
8Fragment specificationth:fragment
9Fragment removalth:remove

第八部分 注释和Blocks

8 Comments and Blocks

8.1 标准的HTML/XML注释

标准的HTML/XML注释是<!-- … -->

<!-- User info follows 这是注释 -->
<div th:text="${...}">
  ...
</div>

8.2 Thymeleaf解析级别(parser-level )的注释

parser-level级别的注释,只能被Thymeleaf在解析模板的时候移除。它是由<!–/ and /–>包裹起来的,Thymeleaf会把所有内容包括<!–/ and /–>移除掉:

<!--/* This code will be removed at Thymeleaf parsing time! */-->

既然被包裹的内容会被移除,那为什么还要用这个注释呢?

考虑这样的场景,当我们仅仅只是打开一个静态的HTML页面,不经过Thymeleaf渲染,为了保持设计内容正常显示我们可以这样使用parser-level注释:

<!--/*--> 
  <div>
     you can see me only before Thymeleaf processes me!
  </div>
<!--*/-->

上面的代码在没有经过Thymeleaf渲染的情况下,依然能够正常显示。

所以举个例子的话,我们可这样使用:

<table>
   <tr th:each="x : ${xs}">
     ...
   </tr>
   <!--/*-->
   <tr>
     ...
   </tr>
   <tr>
     ...
   </tr>
   <!--*/-->
</table>

8.3 原型注释prototype-only comment

原型注释:只在原型里是注释,被解析后就不是注释。

prototype-only类型的注释使用<!–// and //–> 标签包裹,在不使用Thymeleaf渲染的情况下直接打开HTML,注释内容依然是注释,而使用Thymeleaf渲染后,注释的内容得到保留,注释标签被移除,就像下面的例子。

静态页面:

<span>hello!</span>
<!--/*/
  <div th:text="${...}">
    ...
  </div>
/*/-->
<span>goodbye!</span>

经过Thymeleaf解析后的页面:

<span>hello!</span>
 
  <div th:text="${...}">
    ...
  </div>
 
<span>goodbye!</span>

这种诠释的设计应该是考虑到,在页面原型设计的时候,原型使用了一些HTML不支持的属性,所以在前后端分离的时候,为了不影响原型页面的展现将这些HTML不兼容的内容用原型级注释标签注释。

8.4 th:block标签

th:block是Thymeleaf里唯一的元素级处理器。它只是一个属性的容器,允许程序员在th:block标签上定义任何属性。经过Thymeleaf渲染后,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>

经过渲染后th:block标签会被去掉,去掉后整个模板就是HTML合法的了。但是不经过渲染,那么模板就不是HTML合法了。所以为了使模板合法,我们可以这样使用:

<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>

第八部分 代码嵌入

7 代码嵌入

7.1 内联表达式

使用内联表达式[[…]] or [(…)]往文本内容中嵌入表达式,它们的功能分别和th:text or th:utext 一样,可以往内联表达式内部使用任何可以在th:textth:utext里使用的属性。

[[…]]:和th:text一致,它对文本内容进行转义

[(…)]:和th:utext一致,不对文本内容进行转义

来比较以下它们的不同:

如果msg='This is <b>great!</b>'

<p>The message is "[(${msg})]"</p>
的结果是:
<p>The message is "This is <b>great!</b>"</p>

<p>The message is "[[${msg}]]"</p>
的结果是:
<p>The message is "This is &lt;b&gt;great!&lt;/b&gt;"</p>

7.1.1 内联表达式 VS 普通模板

内联表达式,如[(…)]虽然它的代码量比使用th:utext这种普通模板少,而且它们实现同样的功能,但是从网页原型设计的角度考虑,使用普通模板比使用内联表达式更能清晰地、高还原地表达原型设计。

使用普通模板属性,即使是不经过Thymeleaf渲染,它依然能准确地显示原型内容。但是使用内联表达式,你看到的将会是这样的:

Hello, [[${session.user.name}]]!

所以,你可以看到这两者的区别了吧。

7.1.3 禁用内联表达式

使用th:inline="none"禁止Thymeleaf解析内联表达式:

<p th:inline="none">A double array looks like this: [[1, 2, 3], [4, 5]]!</p>

结果如下:

<p>A double array looks like this: [[1, 2, 3], [4, 5]]!</p>

7.2 Text inlining

Text inlining模式需要显式申明th:inline=“text”。其内容下一节讨论

7.3 JavaScript inlining

JavaScript内联允许在HTML模板模式中集成JavaScript脚本块。

和text内联一样,JavaScript内联其实就是在JavaScript模板模式下对脚本内容进行处理(针对JavaScript语法进行表达式解析),因此下一节即将讲到的textual template modes的功能对于JavaScript inlining也是可用的。然而这一节我们先来看看怎么在JavaScript代码块中使用表达式。

首先显式申明th:inline=“javascript”:

<script th:inline="javascript">
    ...
    var username = [[${session.user.name}]];
    ...
</script>

经过解析后的代码:

<script th:inline="javascript">
    ...
    var username = "Sebastian \"Fruity\" Applejuice";
    ...
</script>

从上面的代码可以发现:

  • 第一,JavaScript内联不仅解析了需要的文本内容,还使用分号对代码进行了正确的转义。
  • 第二,之所以会进行转义,是因为我们使用了[[...]]表达式,而如果使用[(...)]表达式,那么结果将如下:
```xml ```

Thymeleaf会把这个注释后面的,分号前面的内容删除,也就是把"Gertrud Kiwifruit"删除,最后解析结果如下:

<script th:inline="javascript">
    ...
    var username = "Sebastian \"Fruity\" Applejuice";
    ...
</script>

这样做的好处是,即使不经过Thymeleaf解析,原始的JavaScript代码模板也是合法的。

7.3.1 高级内联取值和JavaScript序列化

JavaScript的表达式求值并不仅仅局限在String类型上,其他类型的对象也能正确地转换,Thymeleaf支持的对象类型如下:

  • Strings
  • Numbers
  • Booleans
  • Arrays
  • Collections
  • Maps
  • Beans (objects with getter and setter methods)

举个例子,如果使用下面的代码:

<script th:inline="javascript">
    ...
    var user = /*[[${session.user}]]*/ null;
    ...
</script>

${session.user}表达式会正确地对User对象进行求值,然后转换为JavaScript语法格式的内容:

<script th:inline="javascript">
    ...
    var user = {"age":null,"firstName":"John","lastName":"Apricot",
                "name":"John Apricot","nationality":"Antarctica"};
    ...
</script>

JavaScript模板模式下表达式的这种序列化能力由org.thymeleaf.standard.serializer.IStandardJavaScriptSerializer接口的实现类实现,可以通过模板引擎的StandardDialect对象的属性来进行配置。

默认的JS序列化使用了Jackson library。但是如果classpath路径里没有Jackson,那么会使用内置的序列化机制来进行转换。

7.4 CSS inlining

在style标签里使用内联表达式:

classname = 'main elems'
align = 'center'
代码中这样使用:
<style th:inline="css">
    .[[${classname}]] {
      text-align: [[${align}]];
    }
</style>

最终解析后的结果:

<style th:inline="css">
    .main\ elems {
      text-align: center;
    }
</style>

可以看到,[[${classname}]]针对CSS进行了转义,把classname = 'main elems’转换成了main\ elems

7.4.1 CSS natural templates

通过注释来使用内联表达式,就像之前JavaScript中的内联表达式一样:

<style th:inline="css">
    .main\ elems {
      text-align: /*[[${align}]]*/ left;
    }
</style>

第九部分 文本模板的模式

8 文本模板的模式

8.1 文本型语法

在Thymeleaf中支持文本型语法的三种模型是:TEXT, JAVASCRIPTCSS。同时还有两种与它们不同的模板,那就是支持标签语法的HTMLXML

文本型语法和标签型语法的不同之处是,文本型语法中没有标签的支持来表述模板的逻辑,所以文本型语法和标签型语法依赖的解析机制是不同的。

文本型语法最基础的表述方法就是内联。内联在之前的章节其实已经讲到了,而且它非常适合直接在模板中插入文本,比如一个email的模板:

Dear [(${name})],

  Please find attached the results of the report you requested
  with name "[(${report.name})]".

  Sincerely,
    The Reporter.

但是为了实现更多更复杂的逻辑,我们需要一个非标签语法的支持:

[# th:each="item : ${items}"]
  - [(${item})]
[/]

其实上面的模板是下面模板的简化版本:

[#th:block th:each="item : ${items}"]
  - [#th:block th:utext="${item}" /]
[/th:block]

可以注意到我们使用了[#element …] 这样的语法来代表一个元素。这个元素和XML标签语法很像,以 [#element …]表示元素的开始,以[/element]来表示元素的结束,并且独立元素还可以最简化为[#element … /]的形式,只用一个/来表示结束,和XML中的内联元素很像。

标准的唯一可以用来包裹element的是th:block,虽然我们可以通过自定义来实现自己的新element,但是Thymeleaf默认就支持th:block元素,所以不管有没有自定义的元素,th:block元素([#th:block …] … [/th:block])都可以简写为如下的形式:

[# th:each="item : ${items}"]
  - [# th:utext="${item}" /]
[/]

观察上面的代码,[# th:utext="${item}" /]元素里只包含了一个内联的非转义属性th:utext,所以我们的代码可以进一步简化为:

[# th:each="item : ${items}"]
  - [(${item})]
[/]

注意文本型语法需要让每个元素必须有引用属性,而且元素闭合。所以和HTML语法相比,这种语法和XML更像。

接下来看一下JAVASCRIPT模板模式。注意,下面的代码是一个.js文件里的模板,而不是<script>代码块中的模板:

var greeter = function() {

    var username = [[${session.user.name}]];

    [# th:each="salut : ${salutations}"]    
      alert([[${salut}]] + " " + username);
    [/]

};

经过Thymeleaf处理,最终模板如下:

var greeter = function() {

    var username = "Bertrand \"Crunchy\" Pear";

      alert("Hello" + " " + username);
      alert("Ol\u00E1" + " " + username);
      alert("Hola" + " " + username);

};

8.1.1 转义的元素属性

为了避免与处在不同模式下的模板产生交互性影响(比如:在HTML模板里使用text内联语法,如果内联的表达式里使用了<号,那么它会对HTML的结构产生影响),所以Thymeleaf 3.0 支持元素中的属性使用转义后的形式。

  • TEXT 模板模式里的属性必须是HTML转义的.
  • JAVASCRIPT 模板模式里属性必须是 JavaScript-转义的.
  • CSS 模板模式里的属性必须是CSS转义的.

举个例子,下面是TEXT模式下的代码:

[# th:if="${120&lt;user.age}"]
     Congratulations!
[/]

注意到,代码中我们使用了<的转义符&lt;,虽然转义后的模板看起来不太顺眼,但是如果在设计页面原型的时候使用了这样的代码,那么即使在不用Thymeleaf支持的情况下直接打开HTML文件,浏览器也不会吧user.age误认为一个标签元素。

8.2 可扩展性

文本型语法同标签语法一样,同样支持扩展。开发者可以自定义自己的元素和属性,使用时可以套用下面的语法模板:

[#myorg:dosomething myorg:importantattr="211"]some text[/myorg:dosomething]

8.3 在注释中插入代码

JAVASCRIPTCSS模板模式支持在注释语法/[+…+]/ 里插入可被解析的代码(TEXT模式下不支持)。Thymeleaf会在处理模板的时候解除对代码的注释,使代码可用。比如下面的代码:

var x = 23;

/*[+

var msg = "Hello, " + [[${session.user.name}]];

+]*/

var f = function() {
    ...

经过处理后:

var x = 23;

var msg = "Hello, " + [[${session.user.name}]];

var f = function() {
...

8.4 parser-level注释块:移除代码

parser-level其意思就是说,代码内容在Thymeleaf解析的时候才会被当做是注释,然后删除它。这样的注释代码需要被包裹在/[- // -]/符号内:

var x = 23;

/*[- */

var msg  = "This is shown only when executed statically!";

/* -]*/

var f = function() {
...

上面的msg只有在不使用Thymeleaf的情况下才有用。

在TEXT模式下:

...
/*[- Note the user is obtained from the session, which must exist -]*/
Welcome [(${session.user.name})]!
...

8.5 Natural JavaScript and CSS templates

可被解析的注释可以用来构造JavaScript和CSS友好型的代码,比如之前讲到的内联型语法:

...
var username = /*[[${session.user.name}]]*/ "Sebastian Lychee";
...

解析后的结果是:

...
var username = "John Apricot";
...

同样地,我们可以把这种技巧使用到整个文本型语法当中:

  /*[# th:if="${user.admin}"]*/
     alert('Welcome admin');
  /*[/]*/

上面的代码是完全符合JavaScript语法的,当经过Thymeleaf的解析后,如果用户是admin,那么和下面的代码是等价的:

  [# th:if="${user.admin}"]
     alert('Welcome admin');
  [/]

然而你可以发现,/*[# th:if="${user.admin}"]*/代码后面的,分号前面的部分——alert(‘Welcome admin’)并没有被移除,原因是这种删除功能只有内联表达式有。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值