iT邦幫忙

2022 iThome 鐵人賽

DAY 17
0
Modern Web

Google商家大解密就靠網頁設計來加成系列 第 17

[Day17] Thymeleaf 輕鬆入門

  • 分享至 

  • xImage
  •  

Thymeleaf 是一個 Java 伺服器端的 HTML 模板引擎,能直接在 HTML 文件嵌入模板語法進行撰寫,讓開發更加直覺。 可通過 spring-boot-starter-thymeleaf 整合到 SpringBoot 專案,是 JVM Web 開發的理想選擇。

快速入門

讓我們通過簡單的案例快速了解如何使用吧 !

通過 Maven 引入套件

pom.xml 中加入依賴。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

配置

建立配置類別

import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.thymeleaf.dialect.IDialect;
import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.dialect.SpringStandardDialect;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;
import nz.net.ultraq.thymeleaf.layoutdialect.LayoutDialect;

@Configuration 
@EnableWebMvc
@ComponentScan
public class ThymeleafConfig implements WebMvcConfigurer, ApplicationContextAware {
    
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    
    @Bean
    public ITemplateResolver templateResolver() {
        SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
        templateResolver.setApplicationContext(applicationContext);
        // 模板資源路徑的前綴
        templateResolver.setPrefix("classpath:/templates/");
        // 模板資源路徑的後綴
        templateResolver.setSuffix(".html");
        // 編碼
        templateResolver.setCharacterEncoding("utf-8");
        // 快取設定
        templateResolver.setCacheable(false);
        // 模板模式
        templateResolver.setTemplateMode("html5");
        return templateResolver;
    }
    
    @Bean
    public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
        templateEngine.setEnableSpringELCompiler(true);
        
        Set<IDialect> sets = new HashSet<IDialect> ();
        sets.add(new LayoutDialect());
        sets.add(new Java8TimeDialect());
        sets.add( new SpringStandardDialect());
        templateEngine.setDialects(sets);
        
        return templateEngine;
    }
    
    @Bean
    public ViewResolver viewResolver(SpringTemplateEngine engine) {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setTemplateEngine(engine);
        viewResolver.setCharacterEncoding("UTF-8");
        viewResolver.setRedirectHttp10Compatible(false);
        viewResolver.setCache(false);
        viewResolver.setOrder(0);
        return viewResolver;
    }
 
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 靜態資源的路徑
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
    }
}

建立模板

src/main/resources/templates 資料夾下新增模板檔案 hello.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Hello Page</title>
    </head>
    <body>
        <h1>Hello, <span th:text="${userName}"></span>!</h1>
    </body>
</html>

可以看出這不是標準的 HTML 格式,裡面包含了 Thymeleaf 的語法。

為了讓 IDE 可以正確解讀,必須在 <html> 加上 xmlns:th="http://www.thymeleaf.org"

使用模板

建立 Controller 端點

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class MyController {
    @GetMapping("/hello")
    public String hello(Model model) {
        // Model 是模板資料的容器,用 (key, value) 形式儲存
        // 其內的值會被賦予到 Thymeleaf 執行環境
        model.addAttribute("userName", "Joe");
        // 前面已經配置模板資源路徑的規則
        // return 會指到 src/main/resources/templates/hello.html
        return "hello";
    }
}

hello.html 中,有一段是這樣的:

<h1>Hello, <span th:text="${userName}"></span>!</h1>

th:text="${userName}" 表示從 Thymeleaf 執行環境中取變數 userName 的值,以純文字型態取代 HTML 元素的內容。

經過 Thymeleaf 處理後會輸出成:

<h1>Hello, <span>Joe</span>!</h1>

當你從瀏覽器進入 "/hello" 端點,看到的應該是 Hello, Joe! 的文字。

基本語法

Thymeleaf 的語法主要是在 HTML 標籤中嵌入由 th: 開頭的屬性表示使用的 Thymeleaf 功能,並用表達式 (Expression)、字面量 (Literal) 與運算子 (Operator) 對屬性賦值。

如前述範例中的:

<h1>Hello, <span th:text="${userName}"></span>!</h1>

表達式

  • Variable Expressions: ${...} - 從 Thymeleaf 執行環境取變數,範例:

    // 用變數名稱取值
    ${title}
    
    // 變數是 Object 時,可用 (.) 接變數名或 ([]) 包圍變數名取值
    // 等於呼叫 Object 的 Getter
    ${person.father.name}
    ${person['father']['name']}
    
    // 變數是 Map 時,(.) 接變數名或用 ([]) 包圍變數名取值
    // 等於於呼叫 Map 的 get(...)
    ${countriesByCode.ES}
    ${personsByName['Stephen Zucchini'].age}
    
    // 變數是 Array 或 Collection,(.) 接 index 或用 ([]) 包圍 index 取值
    ${personsArray[0].name}
    
    // 可以呼叫物件的方法
    ${person.createCompleteName()}
    ${person.createCompleteNameWithSeparator('-')}
    
  • Selection Variable Expressions: *{...} - 從選擇的物件中取變數,範例:

    // 用 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 th:object="${session.user}">
        // 用 ${...} 表達式,並用 #object 取得已選擇的物件來取變數
        <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>
    
  • Link URL Expressions: @{...} - 用於 URL 連結變數
    支援以下幾種路徑:

    • 絕對 URL: http://www.thymeleaf.org
    • 相對 URL:
      • 環境相對: /itemdetails?id=3 (環境名稱會自動加上)
      • 伺服器相對: ~/billing/processInvoice (用於呼叫同一伺服器上的另一隻程式)
      • 協議相對: //code.jquery.com/jquery-2.0.3.min.js

    範例:

    <!-- 連結會被重寫為 'http://localhost:8080/gtvg/order/details?orderId=3' -->
    <a href="details.html" 
    th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a>
    
    <!-- 連結會被重寫為 '/gtvg/order/details?orderId=3' -->
    <a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>
    
    <!-- 連結會被重寫為 '/gtvg/order/3/details' -->
    <a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>
    

字面量

  • 文字: 'one text', 'Another one!',…
  • 數字: 0, 34, 3.0, 12.3,…
  • 布林值: true, false
  • 空值: null
  • Literal tokens: one, sometext, main,…

數字、布林值、空值其實都算一種 Literal tokens。它用起來跟文字字面量類似,但省略 ('...') 且只能用 (A-Z)(a-z)(0-9)、括號 []、點 (.)、連字號 (-)、底線 (_)

所以

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

可以改寫成

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

運算子

  • 文字運算子:
    • 串聯: +
    • 替換: |The name is ${name}|
  • 算術運算子: +, -, *, /, %
  • 布林運算子: and, or, !, not
  • 比較運算子:
    • 比較: >, <, >=, <= (gt, lt, ge, le)
    • 等於: ==, != (eq, ne)
  • 條件運算子:
    • If-then: (if) ? (then)
    • If-then-else: (if) ? (then) : (else)
    • Default: (value) ?: (defaultvalue)
  • 特殊運算子:
    • 無動作: _

無動作運算子 _ 表示不要做任何動作,例如:

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

使用條件運算子 Default,當 ${user.name}null 時不做動作,直接使用 <span> 內預先設定的內容。

th 屬性

賦值給屬性

有兩種方法:

  1. 我們可以使用 th:attr 賦值給任何屬性,如範例:

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

    th:attr 設定的 action 值會取代原本設定的 subscribe.html,結果會被輸出成:

    <form action="/gtvg/subscribe">
    </form>
    
  2. th:attr 可讀性不好,所以通常會使用其它專門設定特定屬性的 th:* 標籤,如 th:action

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

Thymeleaf 支援大部分 HTML5 屬性的賦值標籤,這裡不全部列出,請參考連結: https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes

即便屬性不在支援列表裡也不用擔心,Thymeleaf 預設對沒定義在標準語法裡的屬性一樣可以用 th:* 的方式賦值。

布林屬性

HTML 中有種特殊的布林屬性,這種屬性沒有值,它本身就代表 true

例如 checked:

<input type="checkbox" name="active" checked />

Thymeleaf 支援這類布林屬性的 th:* 標籤,當設定給標籤的值是 true 時設定屬性,是 false 時則不設定屬性。

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

Thymeleaf 支援的布林屬性這裡不全部列出,請參考連結: https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#fixed-value-boolean-attributes

迴圈

th:each 用於迴圈遍歷物件內的變數,我們用迴圈列出 List 內的資料到 <table> 做範例:

<table>
    <tr>
        <th>NAME</th>
        <th>PRICE</th>
        <th>IN STOCK</th>
    </tr>
    <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}? 'yes' : 'no'">yes</td>
    </tr>
</table>

th:each="prod : ${prods}" 表示迴圈遍歷 ${prods} 內的變數,用 prod 代表當下存取到的變數,重複用此模板片段進行處理。

  • ${prods} 稱為 受迭代變數 (iterated variable)
  • prod 稱為 迭代變數 (iter variable)

若要取得迴圈的狀態資料,有兩種方法:

  1. 使用 Thymeleaf 自動產生的 狀態變數 (status variable) ,變數名為 迭代變數 (iter variable) 名稱加上 Stat 後綴:

    <table>
        <tr>
            <th>NAME</th>
            <th>PRICE</th>
            <th>IN STOCK</th>
        </tr>
        <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}? 'yes' : 'no'">yes</td>
        </tr>
    </table>
    
  2. 在迭代變數後面新增一個變數作為 狀態變數 (status variable) :

    <table>
        <tr>
            <th>NAME</th>
            <th>PRICE</th>
            <th>IN STOCK</th>
        </tr>
        <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}? 'yes' : 'no'">yes</td>
        </tr>
    </table>
    

狀態變數 (status variable) 內有以下資料:

  • 當下的 index。(從 0 開始)
  • 當下的迭代累計數。(從 1 開始)
  • 受迭代變數 (iterated variable) 內含的物件數量。
  • 當下的 迭代變數 (iter variable)
  • 當下的 迭代變數 (iter variable) 是奇數還是偶數,用 even / odd 布林變數確認。
  • 當下的 迭代變數 (iter variable) 是否是迴圈中的第一個,用 first 布林變數確認。
  • 當下的 迭代變數 (iter variable) 是否是迴圈中的最後一個,用 last 布林變數確認。

th:each 可用於以下物件:

  • 任何實作 java.util.Iterable 的物件
  • 任何實作 java.util.Enumeration 的物件
  • 任何實作 java.util.Iterator 的物件
  • 任何實作 java.util.Map 的物件
  • 陣列

條件式

if / unless

th:if 可讓 HTML 片段只在符合條件時出現,例如:

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

th:if="${not #lists.isEmpty(prod.comments)}" 表示只有符合 " prod.comments 不為空 list " 的條件時才出現這個 HTML 片段。

th:if 除了接受布林值外也可用一些特殊的值做為輸入,當符合以下規則時也視為 true:

  • 若值不是 null:
    • 若值是布林,且為 true
    • 若值是數字,且非 0
    • 若值是字元,且非 '0'
    • 若值是字串,且非 “false”, “off”“no”
    • 若值是不是布林、數字、字元、字串。

th:if 還有一個相反的標籤 th:unless,表示只有在條件不符合時才讓片段出現。

前面的範例可以用 th:unless 改寫成:

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

switch / case

th:switch / th:case 可用於根據條件顯示不同內容,範例:

<div th:switch="${user.role}">
  <p th:case="'admin'">User is an administrator</p>
  <p th:case="'manager'">User is a manager</p>
  <p th:case="*">User is some other thing</p>
</div>

th:case="*" 表示預設值。


上一篇
[Day16] Quartz簡介
下一篇
[Day18] AOP-頁面麵包屑的應用
系列文
Google商家大解密就靠網頁設計來加成30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言