Thymeleaf3.0簡易教程

Demo下載,可以在這個demo上進行練習。

1.概述

SpringBoot支持的模板引擎:

  • Thymeleaf(SpringBoot推薦)

  • FreeMarker

  • Velocity

  • Groovy

  • JSP(以前開發Java Web時用的,現在不推薦)

    前後端分離開發雖然已普遍存在,Thymeleaf也可以很好的進行前後臺的協作開發。

使用Thymeleaf 三大理由:

  1. 簡潔漂亮 容易理解
  2. 完美支持HTML5 使用瀏覽器直接打開頁面
  3. 不新增標籤 只需增強屬性

Thymeleaf支持處理六種模板:

  • HTML(默認)
  • XML
  • TEXT
  • JAVASCRIPT
  • CSS
  • RAW

2.添加Thymeleaf依賴

使用Thymeleaf需要在pom.xml文件中添加依賴:

<properties>
        <thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
        <thymeleaf-layout-dialect.version>2.4.1</thymeleaf-layout-dialect.version>
</properties>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

3.SpringBoot配置Thymeleaf

在application.yml或application.properties進行配置

# 是否開啓緩存,開發中設置成false,便於更改文件後,自動刷新
spring.thymeleaf.cache=false
# 檢查模板是否存在
spring.thymeleaf.check-template=true 
# 是否啓用thymeleaf作爲視圖解析
spring.thymeleaf.enabled=true 
# 是否spring el 表達式
spring.thymeleaf.enable-spring-el-compiler=false 
# 模板文件編碼
spring.thymeleaf.encoding=UTF-8
# 指定不解析的視圖名以逗號分隔,
spring.thymeleaf.excluded-view-names= 
# 解析的模板類型
spring.thymeleaf.mode=HTML 
# 模板文件路徑前綴
spring.thymeleaf.prefix=classpath:/templates/ 
# 輸出類型
spring.thymeleaf.servlet.content-type=text/html 
# 文件後綴
spring.thymeleaf.suffix=.html 
#使用在thymeleaf使用消息表達式語法時#{xx},需指定文件名
spring.messages.basename=application

4.模板頁面

SpringBoot默認存放頁面模板的路徑在src/main/resources/templates或者src/main/view/templates,Thymeleaf默認的頁面文件後綴是.html。
Thymeleaf完美支持HTML5 使用瀏覽器直接打開頁面,因爲瀏覽器會忽略它不認識的屬性。絕大多數Standard Dialect處理器都是屬性處理器。Thymeleaf中很多屬性都是th:*這種形式的,這種不是標準的HTML屬性,但是HTML5允許自定義以data-爲前綴的屬性,將其變成data-th-*就會變成合法的屬性。

5.標準表達式語法

所有這些表達式都可以組合和嵌套。

5.1.簡單表達式

5.1.1.#{…}消息表達式

外部化文本就是從模板文件中提取的模板代碼片段,它們可以分開保存,典型的就是保存在.properties文件。因此它們可以被輕易地用其他語言的相應的文本來代替,這就是國際化的處理。外部化文本片段通常被稱爲“message”消息。消息都有一個key來識別它們。Thymeleaf允許我們用#{…}指定text對應的消息。消息表達式的消息會被抽取出來放單獨放在.properties文件中。消息表達式在國際化中最常用。國際化的例子可以參考:《SpringBoot+Thymeleaf實現國際化》

5.1.2.${…}變量表達式

在模板中從WebContext獲取請求參數和請求、會話、屬性:

${x}:返回一個存儲在Thymeleaf Context中的變量x,或者作爲一個請求屬性

${param.x}:返回一個請求參數x(可能是多個值的)

${session.x}:返回一個會話屬性x

${application.x}:返回一個servlet context的屬性x

舉例:

home.html

<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title th:text="#{home.title}">Insert title here</title>
</head>
<body>
<div>
    <a data-th-href="@{/locale(lang=zh_CN)}" th:text="#{home.language.chinese}">中文</a>
    <a data-th-href="@{/locale(lang=en_US)}" th:text="#{home.language.english}">英語</a>
</div>
<h1 data-th-text="#{home.welcome(${userName})}">Fluid jumbotron</h1>
<h1 data-th-text="#{home.introduction(${userBean.name},${userBean.job},${userBean.hobby})}">Hello everyone!</h1>
<h1 data-th-text="${session.mark}">mark</h1>
<h1 data-th-text="${application.remark}">haha</h1>
</body>
</html>

HomeController.java

@Controller
public class HomeController{

    @RequestMapping(value = {"/index","/home","/"},method = RequestMethod.GET)
    public String getHomePage(Model model, Locale locale, HttpServletRequest request) {
        // ${x}
        model.addAttribute("userName","Tome");
        // ${param.x}
        UserBean userBean = new UserBean("Tome","IT Designer","play video game");
        model.addAttribute("userBean",userBean);

        // ${session.x}
        HttpSession session=request.getSession();//獲取session並將mark存入session對象
        session.setAttribute("mark", "歡迎大駕光臨!");

        // ${application.x}
        ServletContext application = request.getServletContext();//獲取ServletContext並將remark存入ServletContext對象
        application.setAttribute("remark","ByeBye!");

        return "home";
    }
}

** th:text默認會轉義特殊字符,th:utext(unescaped text)則不會發生轉義,如 ** :

home.properties

home.general.introduction=Welcome to our <b>fantastic</b> grocery store!

home.html

// 發生轉義:Welcome to our <b>fantastic</b> grocery store!
<p data-th-text="#{home.general.introduction}">good</p>
// 不轉義(實際效果中fantastic會被加粗):Welcome to our fantastic grocery store!
<p data-th-utext="#{home.general.introduction}">good</p>

5.1.3.*{…}選擇變量表達式

選擇表達式就是表達式的結果使用th:object屬性,如:

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

星號和美元符號語法也是可以混用的:

<div th:object="${session.user}">
<p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>

當一個對象選擇所在位置,內部可以用#object來代表它:

<div th:object="${session.user}">
<p>Name: <span th:text="${#object.firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>

當沒有對象選擇時,美元符號和星號語法是等價的:

<div>
<p>Name: <span th:text="*{session.user.name}">Sebastian</span>.</p>
<p>Surname: <span th:text="*{session.user.surname}">Pepper</span>.</p>
<p>Nationality: <span th:text="*{session.user.nationality}">Saturn</span>.</p>
</div>

也就是說,如果沒有對象選擇被執行,那麼${…}與*{…}是等價的:

<div>
<p>Name: <span th:text="*{session.user.name}">Sebastian</span>.</p>
<p>Surname: <span th:text="*{session.user.surname}">Pepper</span>.</p>
<p>Nationality: <span th:text="*{session.user.nationality}">Saturn</span>.</p>
</div>

5.1.4.@{…}鏈接URL表達式

在web應該模板中,URL是很重要的。Thymeleaf專門有一個特殊的@{…}語法來處理它們。這個新語法將用在th:href屬性上。

URL類型:

  • 絕對URL:http://www.thymeleaf.org

  • 相對URL:

    (1)相對的頁面:user/login.html

    (2)相對的上下文(在服務器的上下文名稱會被自動添加):/itemdetails?id=3

    (3)相對的服務器(在同一個服務器上的另一個上下文(相當於另一個應用)裏,允許調用URL):~/billing/processInvoice

    (4)協議相對URL: //code.jquery.com/query-2.3.0.min.js

這些URL表達式的真實處理和轉換都會由Thymeleaf模板引擎處理完成輸出。

舉例:

<!-- Will produce 'http://localhost:8080/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html"
th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a>
<!-- Will produce '/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>
<!-- Will produce '/gtvg/order/3/details' (plus rewriting) -->
<a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>

注意:

  1. @{…}用在th:href上,將會替換掉<a>標籤的href屬性值

  2. 可以給表達式給URL添加參數,如orderId=${o.id},如果有多參數,可以使用逗號隔開@{/order/process(execId=${execId},execType='FAST')}

  3. 變量模板也允許應用在URL上,如@{/order/{orderId}/details(orderId=${orderId})}

  4. 以/開頭 (如: /order/details )的相對URL,將自動以應用程序上下文名稱作爲前綴。

  5. 如果cookie沒有啓用或未知,;jsessionid=...後綴可能會添加到相對URL,因此會話會被保留。這叫URL重寫。Thymeleaf允許插入自己的URL重寫,通過Servlet APIresponse.encodeURL(...)機制。

URL也可以是其他表達式的計算值:

<a th:href="@{${url}(orderId=${o.id})}">view</a>
<a th:href="@{'/details/'+${user.login}(orderId=${o.id})}">view</a>

還有一個額外的語法用來創建server-root-relative(默認是context-root-relative)URL,目的是在同一服務器中,鏈接到不同的上下文。這樣的URL形如:@{~/path/to/something}

5.1.5.~{…}片段表達式

片段表達式可以讓我們將模板公共部分抽取出來作爲片段,然後在需要的地方引入。引入片段通過th:insertth:replace來完成,其中th:include在Thymeleaf3.0開始不再推薦使用,因此不做介紹。

<div th:insert="~{templateName :: fragmentName}"></div>

<div th:replace="~{templateName :: fragmentName}"></div>

包含模板templateName的標記fragmentName。也可以將整個模板templateName包含進來:

<div th:insert="~{templateName}"></div>

還可以插入當前模板裏的片段:

<div th:replace="~{this :: fragmentName}"></div>

<div th:replace="~{ :: fragmentName}"></div>

** 使用th:fragment="fragment"定義片段 **:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div data-th-fragment="copy">
        &copy; 2020 The W Grocery Store
    </div>
</body>
</html>

除了這種方式外,還可以直接使用html的id,如:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div id="copy-section">
        &copy; 2020 The W Grocery Store
    </div>
</body>
</html>

如果用了這種方式,那麼~{…}表達式就要這樣寫:

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

6.常量

  • 文字常量:如’one text’,‘another one’,…

  • 數字常量:如1,34,12.3,…

  • 布爾常量:true、false

    <div data-th-if="${user.isAdmin()} == false">hello</div>
    

    user對象的isAdmin()方法,==false寫在大花括號外,則由Thymeleaf處理,而寫成大花括號內,則將由OGNL/SpringEL引擎來處理,即:

    <div data-th-if="${user.isAdmin() == false}">hello</div>
    
  • Null常量:null

    <div th:if="${variable.something} == null"> </div>
    
  • 字面標記:如one、sometext,main,…

    事實上數字、布爾常量、null都是字面標記的一種特殊形式,字面標記可以由字母、數字、方括號、點、連字符、下劃線組成,不能用空格、逗號。字面標記不需要任何綽號括住:

    <div th:class="content">...</div>
    

7.文本操作

  • 字符串連接:+

    <span th:text="'The name of the user is ' + ${user.name}">
    
  • 文本替換:|The name is {name}|

    <span th:text="|Welcome to our application, ${user.name}!|">
    

    等價於

    <span th:text="'Welcome to our application, ' + ${user.name} + '!'">
    

    用了文本替換,就可以不用字符串連接符+將字符串連接起來了,非常方便。但是請注意,只有變量/消息表達式${…},*{…},#{…}可以出現在文本替換|…|中,其他文字(’…’ )、布爾/數學標記,條件表達式等都不能出現在其中。

8.算術操作符

  • 二元操作符:+,-,*,/,%

    <div th:with="isEven=(${prodStat.count} % 2 == 0)">
    

    以上操作將是Thymeleaf標準表達式引擎處理,如果% 2 == 0出現在大花括號內,則由OGNL/SpringEL引擎來處理:

    <div th:with="isEven=${prodStat.count % 2 == 0}">
    
  • 一元操作符:-

9.比較和相等

  • 比較:>,<,>=,<= (gt,lt,ge,le)

    因爲<,>是html標記的組成部分,不允許出現在屬性部分,因此用&gt; &lt;

    <div th:if="${prodStat.count} &gt; 1">
    <span th:text="'Execution mode is ' + ( (${execMode} == 'dev')? 'Development' : 'Production')">
    
  • 相等:==,!= (eq,ne)

10.條件操作符

  • if-then:(if) ? (then)

    <tr data-th-each="product,dd : ${products}" data-th-class="${dd.odd}? 'odd'">
    
  • if-then-else:(if) ? (then) : (else)

    條件表達式,這三部分可以是變量(${,},*{…}),消息(#{…}),URL(@{…})或文本(’…’)。

    <tr th:class="${row.even}? 'even' : 'odd'">
    ...
    </tr>
    

    條件表達式也可以嵌套:

    <tr th:class="${row.even}? (${row.first}? 'first' : 'even') : 'odd'">
    ...
    </tr>
    

    甚至else部分也可省略,這種情況下,如果條件爲false就返回:

    <tr th:class="${row.even}? 'alt'">
    ...
    </tr>
    
  • default:(value) ?: (defaultvalue)

    默認表達式是一種特殊的條件表達式,當value是null時,就使用defaultvalue。

    <div th:object="${session.user}">
    ...
    <p>Age: <span th:text="*{age}?: '(no age specified)'">27</span>.</p>
    </div>
    

    等價於

    <p>Age: <span th:text="*{age != null}? *{age} : '(no age specified)'">27</span>.</p>
    

    還可在默認值部分嵌套其他條件表達式:

    <p>
    Name:
    <span th:text="*{firstName}?: (*{admin}? 'Admin' : #{default.username})">Sebastian</span>
    </p>
    

11.特殊標記

  • 無操作:_

不做任何操作

<span th:text="${user.name} ?: _">no user authenticated</span>

等價於

<span th:text="${user.name} ?: 'no user authenticated'">...</span>

12.表達式基本對象

在解釋模板時,會生成一些對象,在表達式中使用會使得表達式更加靈活。這些對象使用#號來引用:

  • #ctx:上下文對象

    /*
    * ======================================================================
    * See javadoc API for class org.thymeleaf.context.IContext
    * ======================================================================
    */
    ${#ctx.locale}
    ${#ctx.variableNames}
    /*
    * ======================================================================
    * See javadoc API for class org.thymeleaf.context.IWebContext
    * ======================================================================
    */
    ${#ctx.request}
    ${#ctx.response}
    ${#ctx.session}
    ${#ctx.servletContext}
    
  • #vars 和 #root:與#ctx都是同一個對象,但推薦使用#ctx

  • #locale:上下文區域,直接訪問關聯當前請求的java.util.Locale對象

    ${#locale}
    
  • Web上下文對象

    • #request:直接訪問關聯當前請求的javax.servlet.http.HttpServletRequest對象

      ${#request.getAttribute('foo')}
      ${#request.getParameter('foo')}
      ${#request.getContextPath()}
      ${#request.getRequestName()}
      ...
      
    • #session : 直接訪問關聯當前請求的javax.servlet.http.HttpSession對象

      ${#session.getAttribute('foo')}
      ${#session.id}
      ${#session.lastAccessedTime}
      ...
      
    • #servletContext : 直接訪問關聯當前請求的javax.servlet.ServletContext對象

      ${#servletContext.getAttribute('foo')}
      ${#servletContext.contextPath}
      ...
      
  • 爲Web上下文命令空間提供的request/session屬性

    • param:獲取請求參數
    /*
    * ============================================================================
    * See javadoc API for class org.thymeleaf.context.WebRequestParamsVariablesMap
    * ============================================================================
    */
    ${param.foo}
    // Retrieves a String[] with the values of request parameter 'foo'
    ${param.size()}
    ${param.isEmpty()}
    ${param.containsKey('foo')}
    ...
    
    • session:獲取會話屬性
    /*
    * ======================================================================
    * See javadoc API for class org.thymeleaf.context.WebSessionVariablesMap
    * ======================================================================
    */
    ${session.foo}
    ${session.size()}
    ${session.isEmpty()}
    ${session.containsKey('foo')}
    ...
    
    • application:獲取application/servlet上下文屬性
/*
* =============================================================================
* See javadoc API for class org.thymeleaf.context.WebServletContextVariablesMap
* =============================================================================
*/
${application.foo}
// Retrieves the ServletContext atttribute 'foo'
${application.size()}
${application.isEmpty()}
${application.containsKey('foo')}
...

13.表達式工具類對象

Thymeleaf提供很多工具類對象,幫助我們在表達式中完成一些常見的任務。

  • #execInfo : 提供在Thymeleaf標準表達式中被處理的模板的相關信息的表達式對象

    /*
    * Return the name and mode of the 'leaf' template. This means the template
    * from where the events being processed were parsed. So if this piece of
    * code is not in the root template "A" but on a fragment being inserted
    * into "A" from another template called "B", this will return "B" as a
    * name, and B's mode as template mode.
    */
    ${#execInfo.templateName}
    ${#execInfo.templateMode}
    /*
    * Return the name and mode of the 'root' template. This means the template
    * that the template engine was originally asked to process. So if this
    * piece of code is not in the root template "A" but on a fragment being
    * inserted into "A" from another template called "B", this will still
    * return "A" and A's template mode.
    */
    ${#execInfo.processedTemplateName}
    ${#execInfo.processedTemplateMode}
    /*
    * Return the stacks (actually, List<String> or List<TemplateMode>) of
    * templates being processed. The first element will be the
    * 'processedTemplate' (the root one), the last one will be the 'leaf'
    * template, and in the middle all the fragments inserted in nested
    * manner to reach the leaf from the root will appear.
    */
    ${#execInfo.templateNames}
    ${#execInfo.templateModes}
    /*
    * Return the stack of templates being processed similarly (and in the
    * same order) to 'templateNames' and 'templateModes', but returning
    * a List<TemplateData> with the full template metadata.
    */
    ${#execInfo.templateStack}
    
  • #messages:提供獲取外部化消息的方法

    /*
    * Obtain externalized messages. Can receive a single key, a key plus arguments,
    * or an array/list/set of keys (in which case it will return an array/list/set of
    * externalized messages).
    * If a message is not found, a default message (like '??msgKey??') is returned.
    */
    ${#messages.msg('msgKey')}
    ${#messages.msg('msgKey', param1)}
    ${#messages.msg('msgKey', param1, param2)}
    ${#messages.msg('msgKey', param1, param2, param3)}
    ${#messages.msgWithParams('msgKey', new Object[] {param1, param2, param3, param4})}
    ${#messages.arrayMsg(messageKeyArray)}
    ${#messages.listMsg(messageKeyList)}
    ${#messages.setMsg(messageKeySet)}
    /*
    * Obtain externalized messages or null. Null is returned instead of a default
    * message if a message for the specified key is not found.
    */
    ${#messages.msgOrNull('msgKey')}
    ${#messages.msgOrNull('msgKey', param1)}
    ${#messages.msgOrNull('msgKey', param1, param2)}
    ${#messages.msgOrNull('msgKey', param1, param2, param3)}
    ${#messages.msgOrNullWithParams('msgKey', new Object[] {param1, param2, param3, param4})}
    ${#messages.arrayMsgOrNull(messageKeyArray)}
    ${#messages.listMsgOrNull(messageKeyList)}
    ${#messages.setMsgOrNull(messageKeySet)}
    
  • #uris:爲執行URI/URL操作(轉義/不轉義)提供的工具對象

    /*
    * Escape/Unescape as a URI/URL path
    */
    ${#uris.escapePath(uri)}
    ${#uris.escapePath(uri, encoding)}
    ${#uris.unescapePath(uri)}
    ${#uris.unescapePath(uri, encoding)}
    /*
    * Escape/Unescape as a URI/URL path segment (between '/' symbols)
    */
    ${#uris.escapePathSegment(uri)}
    ${#uris.escapePathSegment(uri, encoding)}
    ${#uris.unescapePathSegment(uri)}
    ${#uris.unescapePathSegment(uri, encoding)}
    /*
    * Escape/Unescape as a Fragment Identifier (#frag)
    */
    ${#uris.escapeFragmentId(uri)}
    ${#uris.escapeFragmentId(uri, encoding)}
    ${#uris.unescapeFragmentId(uri)}
    ${#uris.unescapeFragmentId(uri, encoding)}
    /*
    * Escape/Unescape as a Query Parameter (?var=value)
    */
    ${#uris.escapeQueryParam(uri)}
    ${#uris.escapeQueryParam(uri, encoding)}
    ${#uris.unescapeQueryParam(uri)}
    ${#uris.unescapeQueryParam(uri, encoding)}
    
  • #conversions:允許轉換服務在模板任意地方執行的工具對象

    /*
    * Execute the desired conversion of the 'object' value into the
    * specified class.
    */
    ${#conversions.convert(object, 'java.util.TimeZone')}
    ${#conversions.convert(object, targetClass)}
    
  • #dates:提供java.util.Date相關的工具對象

    /*
    * Format date with the standard locale format
    * Also works with arrays, lists or sets
    */
    ${#dates.format(date)}
    ${#dates.arrayFormat(datesArray)}
    ${#dates.listFormat(datesList)}
    ${#dates.setFormat(datesSet)}
    /*
    * Format date with the ISO8601 format
    * Also works with arrays, lists or sets
    */
    ${#dates.formatISO(date)}
    ${#dates.arrayFormatISO(datesArray)}
    ${#dates.listFormatISO(datesList)}
    ${#dates.setFormatISO(datesSet)}
    /*
    * 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')}
    /*
    * Obtain date properties
    * Also works with arrays, lists or sets
    */
    ${#dates.day(date)}
    ${#dates.month(date)}
    ${#dates.monthName(date)}
    ${#dates.monthNameShort(date)}
    ${#dates.year(date)}
    ${#dates.dayOfWeek(date)}
    ${#dates.dayOfWeekName(date)}
    ${#dates.dayOfWeekNameShort(date)}
    ${#dates.hour(date)}
    ${#dates.minute(date)}
    ${#dates.second(date)}
    ${#dates.millisecond(date)}
    /*
    * Create date (java.util.Date) objects from its components
    */
    ${#dates.create(year,month,day)}
    ${#dates.create(year,month,day,hour,minute)}
    ${#dates.create(year,month,day,hour,minute,second)}
    ${#dates.create(year,month,day,hour,minute,second,millisecond)}
    /*
    * Create a date (java.util.Date) object for the current date and time
    */
    ${#dates.createNow()}
    ${#dates.createNowForTimeZone()}
    /*
    * Create a date (java.util.Date) object for the current date (time set to 00:00)
    */
    ${#dates.createToday()}
    ${#dates.createTodayForTimeZone()}
    /*
    * Create a date (java.util.Date) object for the current date (time set to 00:00)
    */
    ${#dates.createToday()}
    ${#dates.createTodayForTimeZone()}
    
  • #calendars:類似#dates

    /*
    * 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)}
    //
    ${#calendars.month(date)}
    //
    ${#calendars.monthName(date)}
    //
    ${#calendars.monthNameShort(date)}
    //
    ${#calendars.year(date)}
    //
    ${#calendars.dayOfWeek(date)}
    //
    ${#calendars.dayOfWeekName(date)}
    //
    ${#calendars.dayOfWeekNameShort(date)} //
    ${#calendars.hour(date)}
    //
    ${#calendars.minute(date)}
    //
    ${#calendars.second(date)}
    //
    ${#calendars.millisecond(date)}
    
    /*
    * 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()}
    
  • #numbers:數字對象的工具類

    /*
    * ==========================
    * 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')}
    /*
    * =====================
    * Formatting currencies
    * =====================
    */
    ${#numbers.formatCurrency(num)}
    ${#numbers.arrayFormatCurrency(numArray)}
    ${#numbers.listFormatCurrency(numList)}
    ${#numbers.setFormatCurrency(numSet)}
    /*
    * ======================
    * Formatting percentages
    * ======================
    */
    ${#numbers.formatPercent(num)}
    ${#numbers.arrayFormatPercent(numArray)}
    ${#numbers.listFormatPercent(numList)}
    ${#numbers.setFormatPercent(numSet)}
    /*
    * Set minimum integer digits and (exact) decimal digits.
    */
    ${#numbers.formatPercent(num, 3, 2)}
    ${#numbers.arrayFormatPercent(numArray, 3, 2)}
    ${#numbers.listFormatPercent(numList, 3, 2)}
    ${#numbers.setFormatPercent(numSet, 3, 2)}
    /*
    * ===============
    * Utility methods
    * ===============
    */
    /*
    * Create a sequence (array) of integer numbers going
    * from x to y
    */
    ${#numbers.sequence(from,to)}
    ${#numbers.sequence(from,to,step)}
    
  • #strings :String對象的工具類

    /*
    * Null-safe toString()
    */
    ${#strings.toString(obj)}
    // also array*, list* and set*
    /*
    * 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)}
    /*
    * Perform an 'isEmpty()' check on a string and return it if false, defaulting to
    * another specified string if true.
    * Also works with arrays, lists or sets
    */
    ${#strings.defaultString(text,default)}
    ${#strings.arrayDefaultString(textArr,default)}
    ${#strings.listDefaultString(textList,default)}
    ${#strings.setDefaultString(textSet,default)}
    /*
    * Check whether a fragment is contained in a String
    * Also works with arrays, lists or sets
    */
    ${#strings.contains(name,'ez')}
    ${#strings.containsIgnoreCase(name,'ez')}
    /*
    * Check whether a String starts or ends with a fragment
    * Also works with arrays, lists or sets
    */
    ${#strings.startsWith(name,'Don')}
    ${#strings.endsWith(name,endingFragment)}
    /*
    * Substring-related operations
    * Also works with arrays, lists or sets
    */
    ${#strings.indexOf(name,frag)}
    ${#strings.substring(name,3,5)}
    ${#strings.substringAfter(name,prefix)}
    ${#strings.substringBefore(name,suffix)}
    ${#strings.replace(name,'las','ler')}
    /*
    * Append and prepend
    * Also works with arrays, lists or sets
    */
    ${#strings.prepend(str,prefix)}
    ${#strings.append(str,suffix)} // also array*, list* and set*
    // also array*, list* and set*
    /*
    * Change case
    * Also works with arrays, lists or sets
    */
    ${#strings.toUpperCase(name)}
    ${#strings.toLowerCase(name)} // also array*, list* and set*
    // also array*, list* and set*
    /*
    * Split and join
    */
    ${#strings.arrayJoin(namesArray,',')}
    ${#strings.listJoin(namesList,',')}
    ${#strings.setJoin(namesSet,',')}
    ${#strings.arraySplit(namesStr,',')}
    ${#strings.listSplit(namesStr,',')}
    ${#strings.setSplit(namesStr,',')} // returns String[]
    // returns List<String>
    // returns Set<String>
    /*
    * Trim
    * Also works with arrays, lists or sets
    */
    ${#strings.trim(str)} // also array*, list* and set*
    /*
    * Compute length
    * Also works with arrays, lists or sets
    */
    ${#strings.length(str)} // also array*, list* and set*
    /*
    * Abbreviate text making it have a maximum size of n. If text is bigger, it
    * will be clipped and finished in "..."
    * Also works with arrays, lists or sets
    */
    ${#strings.abbreviate(str,10)}
    // also array*, list* and set*
    /*
    * Convert the first character to upper-case (and vice-versa)
    */
    ${#strings.capitalize(str)}
    // also array*, list* and set*
    ${#strings.unCapitalize(str)}
    // also array*, list* and set*
    /*
    * Convert the first character of every word to upper-case
    */
    ${#strings.capitalizeWords(str)}
    // also array*, list* and set*
    ${#strings.capitalizeWords(str,delimiters)}
    // also array*, list* and set*
    /*
    * Escape the string
    */
    ${#strings.escapeXml(str)}
    ${#strings.escapeJava(str)}
    ${#strings.escapeJavaScript(str)}
    ${#strings.escapeJavaScript(str)}
    ${#strings.unescapeJava(str)}
    ${#strings.unescapeJavaScript(str)}
    
    /*
    * Null-safe comparison and concatenation
    */
    ${#strings.equals(first, second)}
    ${#strings.equalsIgnoreCase(first, second)}
    ${#strings.concat(values...)}
    ${#strings.concatReplaceNulls(nullValue, values...)}
    /*
    * Random
    */
    ${#strings.randomAlphanumeric(count)}
    
  • #objects:對象的工具類

    /*
    * Return obj if it is not null, and default otherwise
    * Also works with arrays, lists or sets
    */
    ${#objects.nullSafe(obj,default)}
    ${#objects.arrayNullSafe(objArray,default)}
    ${#objects.listNullSafe(objList,default)}
    ${#objects.setNullSafe(objSet,default)}
    
  • #bools:布爾計算的工具類

    /*
    * Evaluate a condition in the same way that it would be evaluated in a th:if tag
    * (see conditional evaluation chapter afterwards).
    * Also works with arrays, lists or sets
    */
    ${#bools.isTrue(obj)}
    ${#bools.arrayIsTrue(objArray)}
    ${#bools.listIsTrue(objList)}
    ${#bools.setIsTrue(objSet)}
    /*
    * Evaluate with negation
    * Also works with arrays, lists or sets
    */
    ${#bools.isFalse(cond)}
    ${#bools.arrayIsFalse(condArray)}
    ${#bools.listIsFalse(condList)}
    ${#bools.setIsFalse(condSet)}
    /*
    * Evaluate and apply AND operator
    * Receive an array, a list or a set as parameter
    */
    ${#bools.arrayAnd(condArray)}
    ${#bools.listAnd(condList)}
    ${#bools.setAnd(condSet)}
    /*
    * Evaluate and apply OR operator
    * Receive an array, a list or a set as parameter
    */
    ${#bools.arrayOr(condArray)}
    ${#bools.listOr(condList)}
    ${#bools.setOr(condSet)}
    
  • #arrays:數組工具類

    /*
    * 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)}
    
  • #lists:列表的工具類

    /*
    * 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)}
    
  • #sets:集合工具類

    /*
    * 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)}
    
  • #maps:映射工具類

    /*
    * 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)}
    
  • #aggregates:對數組和集合進行的統計的工具類

    /*
    * Compute sum. Returns null if array or collection is empty
    */
    ${#aggregates.sum(array)}
    ${#aggregates.sum(collection)}
    /*
    * Compute average. Returns null if array or collection is empty
    */
    ${#aggregates.avg(array)}
    ${#aggregates.avg(collection)}
    
  • #ids:處理id屬性的工具類

    /*
    * Normally used in th:id attributes, for appending a counter to the id attribute value
    * so that it remains unique even when involved in an iteration process.
    */
    ${#ids.seq('someId')}
    /*
    * Normally used in th:for attributes in <label> tags, so that these labels can refer to Ids
    * generated by means if the #ids.seq(...) function.
    *
    * Depending on whether the <label> goes before or after the element with the #ids.seq(...)
    * function, the "next" (label goes before "seq") or the "prev" function (label goes after
    * "seq") function should be called.
    */
    ${#ids.next('someId')}
    ${#ids.prev('someId')}
    

舉例:

<p>
Today is: <span th:text="${#calendars.format(today,'dd MMMM yyyy')}">13 May 2011</span>
</p>

HomeController.java

@Controller
public class HomeController{

    @RequestMapping(value = {"/index","/home","/"},method = RequestMethod.GET)
    public String getHomePage(Model model, Locale locale, HttpServletRequest request) {
        model.addAttribute("today",new Date());
        return "home";
    }
}

14.數據轉換和格式化

Thymeleaf爲變量表達式(......{...})和選擇表達式(*{...})定義了一個雙花括號語法:{{…}},*{{…}},允許我們使用配置好的轉換服務對數據進行轉換。基本形式:

<td th:text="${{user.lastAccessDate}}">...</td>

15.給任意屬性設置值

很可惜,這種給屬性值設置值的方式,在模板中並不常用。這種方式通過th:attr就能夠改變標記的屬性的值:

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

th:attr用逗號分隔開每個屬性:

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

16.給具體屬性設置值th:*

如設置標記的value屬性:

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

再如form標記的action屬性:

<form action="subscribe.html" th:action="@{/subscribe}">

還有更多,請參考Thymeleaf官方的相關文檔。

17.迭代

17.1.th:each迭代屬性

舉例

ProductListController.java

@Controller
public class ProductListController {

    @RequestMapping(value = {"product/list"},method = RequestMethod.GET)
    public String showProducts(Model model, Locale locale, HttpServletRequest request) {

        ProductService productService = new ProductService();
        List<Product> products = productService.findAll();
        model.addAttribute("products",products);
        return "product/list";
    }
}

product/list.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <link rel="stylesheet" type="text/css" media="all" data-th-href="@{/css/wgrocery.css}" />
    <title>Title</title>
</head>
<body>
<h1>Product List</h1>
<table>
    <thead>
    <tr>
        <th>Name</th>
        <th>Price</th>
        <th>In Stock</th>
        <th>Comments</th>
    </tr>
    </thead>
    <tbody data-th-remove="all-but-first">
        <!--`product : ${products}`意思是爲 ${products}中的每一個元素重複這個模板片段,並通過product變量使用當前元素。-->
    <tr data-th-each="product : ${products}" data-th-class="${productStat.odd}? 'odd'">
        <td data-th-text="${product.name}"></td>
        <td data-th-text="${product.price}"></td>
        <td data-th-text="${product.inStock} ? #{true} : ${false}"></td>
        <td>
            <span data-th-text="${#lists.size(product.comments)}">0</span> comment/s
            <a href="comments.html" data-th-href="@{/product/comments(prodId=${product.id})}"
               data-th-unless="${#lists.isEmpty(product.comments)}">view</a>
        </td>
    </tr>
</tbody>

</table>
<p>
    <a href="../home.html" th:href="@{/home}">Return to home</a>
</p>

</body>
</html>

我們在list.html使用th:each迭代products列表。product : ${products}意思是爲 ${products}中的每一個元素重複這個模板片段,並通過product變量使用當前元素。

  • ${products}:我們稱其爲被迭代表達式或被迭代變量
  • product:我們稱其爲迭代變量或中間變量

### 17.2什麼對象可以被迭代呢?

  1. 任何實現了java.util.Iterable的對象
  2. 任何實現了java.util.Enumeration的對象
  3. 任何實現了java.util.Iterator的對象
  4. 任何實現了java.util.Map的對象
  5. 任何數組

17.3獲取迭代狀態

當使用th:each時,Thymeleaf提供一些狀態變量跟蹤迭代的狀態。這些狀態變量在th:each屬性中定義的,包括以下這一些:

  1. index:當前迭代的索引,從0開始
  2. count:當前迭代的索引,從1開始
  3. size:被迭代變量中元素總數
  4. current:每次迭代的迭代變量
  5. even/odd:檢查當前迭代是雙數還是單數,返回true或false
  6. first:檢查當前迭代是否是第一個
  7. last:檢查當前迭代是否是最後一個

17.3.1如何獲得這些狀態變量呢?

<tr data-th-each="product,dd : ${products}" data-th-class="${dd.odd}? 'odd'">...</tr>

狀態變量是定義在th:each屬性當中的,跟在迭代變量後,用逗號分開,如上面的dd就是狀態變量。事實上,如果我們不顯示定義我們的狀態變量,Thymeleaf也會幫我們創建一個,它的名稱是迭代變量+Stat後綴,如下所示:

<tr data-th-each="product : ${products}" data-th-class="${productStat.odd}? 'odd'">...</tr>

上面沒有顯示定義狀態變量,所以默認的狀態變量是productStat。

18.數據懶加載

當數據需要時,再加載。爲了支持懶加載,Thymeleaf提供了懶加載上下文變量,上下文變量實現了ILazyContextVariable接口。在大多數情況下,都直接繼承LazyContextVariable的默認實現。舉個例子:

ProductListController.java

@Controller
public class ProductListController {

    @RequestMapping(value = {"product/list"},method = RequestMethod.GET)
    public String showProducts(Model model, boolean show) {

        ProductService productService = new ProductService();
        // 數據懶加載
        model.addAttribute("products", new LazyContextVariable<List<Product>>() {
            @Override
            protected List<Product> loadValue() {
                return productService.findAll();
            }
        });
        model.addAttribute("show",show);
        return "product/list";
    }
}

list.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <link rel="stylesheet" type="text/css" media="all" data-th-href="@{/css/wgrocery.css}" />
    <title>Title</title>
</head>
<body>
<!-- 如果是true,則進行入執行-->
<table data-th-if="${show}">
    <thead>
    <tr>
        <th>Name</th>
        <th>Price</th>
        <th>In Stock</th>
        <th>Comments</th>
    </tr>
    </thead>
    <tbody data-th-remove="all-but-first">
    <tr data-th-each="product : ${products}" data-th-class="${productStat.odd}? 'odd'">
        <td data-th-text="${product.name}"></td>
        <td data-th-text="${product.price}"></td>
        <td data-th-text="${product.inStock} ? #{true} : ${false}"></td>
        <td>
            <span data-th-text="${#lists.size(product.comments)}">0</span> comment/s
            <a href="comments.html" data-th-href="@{/product/comments(prodId=${product.id})}"
               data-th-unless="${#lists.isEmpty(product.comments)}">view</a>
        </td>
    </tr>
</tbody>
</table>
</body>
</html>
  • http://localhost:8080/product/list?show=true:加載數據
  • http://localhost:8080/product/list?show=false:不加載數據

如果Controller裏,沒有使用如下懶加載:

// 懶加載
        model.addAttribute("products", new LazyContextVariable<List<Product>>() {
            @Override
            protected List<Product> loadValue() {
                return productService.findAll();
            }
        });

那麼無論是http://localhost:8080/product/list?show=true還是http://localhost:8080/product/list?show=false都會觸發數據加載,區別僅在於有沒有顯示出來而已。

19.條件屬性th:if和th:unless

有時模板裏的部分代碼,你只想在符合某種條件下時才顯示。如上面的產品列表裏,如果評論數據不爲0時,則顯示view按鈕:

<a href="comments.html" data-th-href="@{/product/comments(prodId=${product.id})}"
               data-th-if="not ${#lists.isEmpty(product.comments)}">view</a>

th:if屬性的表達式的值爲true的情況,遵循以下規則:

  • 如果值爲非null,表達式的值就是true

    1. 如果值是布爾類型,並且值是true
    2. 如果值是數字類型,並且值是非0的情況
    3. 如果值是字符類型,並且值是非0的情況
    4. 如果值是字符串類型,並且值不是“false”、“off”、“no”的情況
    5. 如果值不布爾值、數字值、字符值、字符串值的情況
  • 如果值是null,那麼th:if的值是false

th:if有一個相反的屬性th:unless:

<a href="comments.html" data-th-href="@{/product/comments(prodId=${product.id})}"
               data-th-if="not ${#lists.isEmpty(product.comments)}">view</a>

等價於

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

20.switch聲明th:switch和th:case

只匹配一個,th:case="*"是默認選項。

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

21.模板佈局

21.1模板片段

將模板中的公共部分抽取出來單獨放在一個html中,然後在模板中引用。這種方式有利用我們複用代碼、修改代碼、維護代碼。如將模板網頁的header或footer、menus抽取出來,可以這樣做使用th:fragment屬性定義片段:

  • 定義片段th:fragment

    片段也是一個完整的html。下面我們在footer.html裏定義了一個片段copy

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <div data-th-fragment="copy">
            $copy; 2020 The W Grocery Store
        </div>
    </body>
    </html>
    
  • 在模板中包含這些片段

    在模板中使用th:insertth:replaceth:include也可以,但在Thymeleaf3.0開始不再推薦使用)

    我們在模板list.html使用copy片段:

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

    等價於

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

    等價於

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

    等價於

    <body>
    ...
    <div th:replace="footer :: copy"></div>
    </body>
    

th:insertth:replaceth:include的區別:

有以下這樣一個片段:

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

21.2.參數化片段簽名

讓片段有類似函數的機制。定義參數化片段:

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

使用th:insertth:replace調用參數化片段

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

如果片段定義是沒有參數,如下面這樣的:

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

那麼我們只能用第二種方式來引用,即:

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

等價於th:replaceth:with的組合

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

21.3模板內的斷言

斷言屬性th:assert可以指定一個以逗號分隔的表達式列表,列表中的表達式必須都爲true,否則就會報異常:

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

這個屬性可以很方便驗證片段簽名的參數:

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

21.4靈活的佈局

我們可以指定參數,這個參數是某個片段來達到靈活佈局的目的:

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

引用以上片段:

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

如果相保留模板中的,而不要片段中的,那麼片段可以傳個空的,如:

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

如果是想保留片段中的默認值,可以指定它對相應的參數不操作,如:

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

那麼片段中的title標記的默認值就會被保留。

21.5有條件插入片段

根據不同角色選用不同的片段:

<!--匹配上,則用片段,否則插入空片段-->
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : ~{}">...</div>
<!--匹配上,則用片段,否則不改變模板標記-->
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : ~{}">...</div>

21.6移除模板片段

使用th:remove屬性移除片段,這個屬性有5種行爲方式:

  1. all:移除包含標籤及其孩子標籤
  2. body:不移除包含標籤,只移除其孩子標籤
  3. tag:只移除包含標籤,不移除其孩子標籤
  4. all-but-first:移除包含標籤的所有孩子標籤,除了第一個之外
  5. none:什麼也不做

移除也可以帶條件的:

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

由上可知th:remove屬性會視null爲none:

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

21.7片段的繼承

定義一個片段layout,有兩個參數title和content:

<!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也會被各自替換。

22.本地變量

一般可以在th:each迭代時定義本地變量,其實可以用th:with定義本地變量:

<div th:with="firstPer=${persons[0]}">
<p>
The name of the first person is <span th:text="${firstPer.name}">Julius Caesar</span>.
</p>
</div>

也可以同時定義多個本地變量:

<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:with屬性允許重複利用本地變量:

<div th:with="company=${user.company + ' Co.'},account=${accounts[company]}">...</div>

23.th:block塊屬性

Thymeleaf中唯一一個作爲元素處理器的屬性th:block。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>

本地變量user可以在th:block包圍的範圍內使用。

24.內聯表達式

如果有一個標籤是這樣寫的:

<p>Hello, <span th:text="${session.user.name}">Sebastian</span>!</p>

它完成span標籤來輔助完成,其實內聯表達式就可以完美達到這一點:

<p>Hello, [[${session.user.name}]]!</p>

等價於

<p>Hello, [(${session.user.name})]!</p>

[[...]][(...)] 這兩種內聯表達式可以讓我們直接將表達式寫到html中去。

[[...]]對應th:text,[(...)]對應th:utext

25.禁用內聯表達式

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

26.文本內聯

需要顯示啓用。與上面看到的內聯表達式差不多,但更強大。

<div th:inline="text">
    ...
</div>

27.javascript內聯

需要顯示啓用。可以更好的對標籤<script>進行集成。

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

28.CSS內聯

需要顯示啓用。可以更好的對標籤<style>進行集成。

<style th:inline="css">
.[[${classname}]] {
text-align: [[${align}]];
}
</style>

上面就是Thymeleaf3.0的大概教程了。學習完,也基本掌握Thymeleaf3.0了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章