Day 14 - Spring Boot & Thymeleaf

Thymeleaf 是Spring Boot 推荐使用的前端模板引擎,它除了可以完全取代JSP 外,还有以下三个优点。

  1. Thymeleaf 支援HTML 原型并在HTML 标签增加额外的属性来达到模板资料的展示方式,这使得它不管在有网路或无网路的环境下都可以运行。
    因为浏览器解析HTML 时会忽略未定义的标签属性,所以可以在浏览器检视页面的静态效果,也因为当有资料返回到页面时,Thymeleaf 会动态地替换掉静态内容,所以也可以在服务器检视带有资料的动态效果。
  2. Thymeleaf 有开箱即用的特性,它提供标准(Standard dialects)和Spring 标准(Spring Standard dialects)两种方言,可以直接套用模板实现JSTL、OGNL 表示式效果,避免每天套模板、改标签等困扰,同时开发人员也可以创建自定义的标签。
  3. Thymeleaf 提供Spring 标准方言和Spring MVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。

常用表达式

变量表达式

变量表达式即OGNL 表达式或Spring EL 表达式,传入控制器设定的name 值

<span th:text="${name}"></span>

选择表达式

选择表达式跟变量表达式很像,不过它是用一个预先选择的对象来取代上下文变数容器执行

<div th:Object="${book}">
	...
	<span>[[*{title}]]</span>
	...
</div>

文字国际化表达式

文字国际化表达式允许我们从一个外部档案获取文字资讯,通过键值来引用文字讯息,在Spring 中,可自动与Spring 的MessageSource 机制集成。

<table>
	...
	<th th:text="#{header.address.city}">...</th>
	...
</table>

若希望表达式由上下文变量的值决定,或希望将变量指定为参数,则可以在表达式中使用变量表达式。

#{${config.adminWelcomeKey}(${session.user.name})}

URL 表达式

URL 表达式指的是把一个有用的上下文或回话资讯新增到URL,这个过程经常被叫做URL 重写。

<link rel="stylesheet" th:href="@{/css/reset.css}">

可以设定参数

<a th:href="@{/order/details(id=${orderId})}"></a>

其结果为

<a href="/order/details?id=1">...</a>

也可以为相对路径,但应用程序上下文不会被加到URL 前面

<a th:href="@{../documents/report}"></a>

还可以是与服务器的相对路径,同样应用程序上下文不会被加到URL 前面

<a th:href="@{~/contents/main}"></a>

当然,也可以设定为绝对路径

<a th:href="@{http://www.mycompany.com/main}">...</a>

常用标签

因为标签的使用方法大多为th: 加上HTML 标签後再交由表达式赋值或判断,因次这边只列出比较特殊的使用范例。

物件操作

  1. th:object : 用於接收後台传过来的物件
  2. th:field : 用於绑定後台物件和表单资料,可同时设定id 和name 为後台物件属性,且value 为该属性值。
<form th:object="${user}">
	<input th:field="*{account}"/>
	<input th:field="*{password}"/>
</form>

HTML 解析结果如下

<form>
	<input id="account" name="account" value="account"/>
	<input id="password" name="password" value="password"/>
</form>

共用程序区块

  1. th:fragment : 标记重复程序区块。
    假设有一区块设定为block.html,其内容为

    <div th:fragment="block">
    	<h1>Thymeleaf Block</h1>
    </div>
    
  2. th:insert : 区块插入所属标签中。

    <section th:insert="block :: block"></section>
    

    效果为

    <section>
    	<div>
    		<h1>Thymeleaf Block</h1>
    	</div>
    </section>
    
  3. th:replace : 区块取代所属标签。

    <section th:replace="block :: block"></section>
    

    效果为

    <div>
    	<h1>Thymeleaf Block</h1>
    </div>
    
  4. th:include : 区块内容插入所属标签中(3.0 开始不建议使用)。

    <section th:include="block :: block"></section>
    

    效果为

    <section>
    	<h1>Thymeleaf Block</h1>
    </section>
    

回圈

th:each : 遍历整个List 或Array 物件。

<select th:each="item : ${list}">
	<option th:value="${item.value}">[[${item.name}]]</option>
</select>

条件判断

  1. th:if : 条件成立时显示。
  2. th:unless : 条件不成立时显示。
    假设有一条件为判断User 是否存在
<div>
	<span th:if="${user == null}">User 不存在</span>
	<span th:unless="${user == null}">User 存在</span>
</div>

当User 存在时,效果为

<div>
	<span>User 存在</span>
</div>
  1. Switch 判断

    1. th:switch : 多选择判断,指定case 判断变数。
    2. th:case : 判断switch 指定变数为何。
      假设有一条件为判断User 权限为何
    <div th:switch="${user.role}">
    	<span th:case="admin">管理员</span>
    	<span th:case="vip">VIP 会员</span>
    	<span th:case="*">普通会员</span>
    </div>
    

    当User 的role 不为admin 或vip 时,效果为

    <div>
    	<span>普通会员</span>
    </div>
    

实作

新增依赖

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

配置Thymeleaf 属性

# 表示是否启用Thymeleaf 的快取
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.resources.static-locations=classpath:/static/

CSS 共同资源

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="css">
    <link rel="stylesheet" th:href="@{/css/reset.css}">
    <link rel="stylesheet" th:href="@{/css/main.css}">
</head>
</html>

JS 共同资源

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="js">
    <script th:src="@{/js/lib/jquery-3.4.1.js}"></script>
    <script th:src="@{/js/validation.js}"></script>
</head>
</html>

登入页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>会员登入</title>
    <head th:insert="load/css :: css"></head>
    <head th:insert="load/js :: js"></head>
    <link rel="stylesheet" th:href="@{/css/login.css}">
</head>
<body>
    <main>
        <h1>会员登入</h1>
        <form id=loginForm method="POST" th:action="@{/login}" th:Object="${memberAccount}" enctype="multipart/form-data" autocomplete="off">
            <section class="form-input">
                <label>帐号/Email</label>
                <div class="input-area">
                    <div class="input-img"><img th:src="@{/images/person.png}"></div>
                    <input type="text" th:field="*{username}" placeholder="请输入您的Email"/>
                </div>
            </section>
            <section class="form-input">
                <label>密码</label>
                <div class="input-area">
                    <div class="input-img"><img th:src="@{/images/lock.png}"></div>
                    <input type="password" th:field="*{password}" maxlength="16" placeholder="请输入您的密码"/>
                </div>
            </section>
            <button>登入</button>
            <span>还不是会员吗?<a th:href="@{/register}">立即加入</a></span>
        </form>
    </main>

    <script th:src="@{/js/login.js}"></script>
</body>
</html>

Controller 响应页面

package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.example.demo.entity.MemberAccount;
import com.example.demo.vo.MemberAccountVO;

@Controller
public class MemberAccountController {

	@RequestMapping(value = {"/", "/login"}, method = RequestMethod.GET)
	public String login(@ModelAttribute MemberAccount memberAccount) {
		
		return "login";
	}
	
}

Github

新增Thymeleaf 依赖及其配置
新增所需HTML 档案及相关静态资源与功能配置

参考网站

Thymeleaf标准方言


<<:  入门魔法 - Event 事件

>>:  GitHub Gist - 好用的分享、内签资讯分享工具

110/16 - 整合Android 6到Android 11

都把权限写完了,该来做个小整理,这次我们整合Android 6到Android 11,没有Andro...

预编译 - 变数和function的被建立、初始化/预编译、执行的全纪录

你以为JS拿来就乖乖照着我们打的一行一行跑吗?太天真了,我说我~~ 变数怎麽存,存哪里,在哪里叫得到...

参考监视器的非必需属性-高凝聚力(High cohesion)

-安德森报告和TCSEC 1972年,James P. Anderson&Co.在着名的Ander...

Day13 - 重构产品页面 API,使用 API routes - feat. MongoDB

重构产品页面 API 在这个章节中,我们将使用 API routes 重构在前面章节中撰写的「产品列...

离职倒数1天:铁人赛心得

今天是最後一篇 我居然写完了 中间还去了屋久岛 三天两夜完全没网路 一天打越洋电话回台湾叫朋友帮忙发...