这次我们学习灵活的布局,或者说提高代码的复用性。
无论你浏览什么网页,都会(基本上)看到它------没错,它就是导航栏。
以上列举了 4 个网站的导航栏 ,在网站内部跳转时,它们基本上不变,或只是变更少数内容。
要为网站的每个页面都添上导航栏,必定会造成重复的代码,一个好的模板怎么会容忍这种事情发生?!于是, Thymeleaf 马上就提供了 th:fragment 属性来避免这种事的发生,现在我们来试试
假如有一个简易(过于简陋了,将来会解决这个问题)的导航栏:用来在网站内跳转不同的页面。
我们要在网站的每个页面都添加上(例如:主页面2、订阅点这里、主页面 的跳转链接)
先单独创建一个 menu.html 文件,专门写导航栏的代码:(也可以直接写在某一个页面内)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Good Thymes Virtual Grocery</title>
</head>
<body>
<menu th:fragment="menu-copy">
<p>menu</p>
<a href="/home2.html"
th:href="@{/home2}">主页面2</a>
<a href="/subscribe"
th:href="@{/subscribe}">订阅点这里</a>
<a href="/"
th:href="@{/}">主页面</a>
</menu>
</body>
</html>
并给主要标签加上 th:fragment 属性,值随便取一个,就叫 menu-copy 。
接着在 home.html(主页 html)、home2.html(主页2 html)、subscribe.html(订阅界面 html),文件中加入:
<div th:insert="~{menu :: menu-copy}"></div>
或:
<div th:replace="~{menu :: menu-copy}"></div>
或:
<div th:include="~{menu :: menu-copy}"></div>
也可以把 ~{} 去掉,但是 th:include 属性在 Thymeleaf 3.0之后就不推荐使用了,那它们的区别是什么?
th:insert 是最简单的,它将目标标签作为自己本地标签的子标签插入进来,带目标标签的属性。
th:replace 直接把本地标签换成了目标标签,带属性。
th:include 只是将目标标签的内容(例如它的子标签)插入到本地标签来,不带属性。
所以上述代码结果分别为:
<div>
<menu th:fragment="menu-copy">
<p>menu</p>
<a href="/home2.html"
th:href="@{/home2}">主页面2</a>
<a href="/subscribe"
th:href="@{/subscribe}">订阅点这里</a>
<a href="/"
th:href="@{/}">主页面</a>
</menu>
</div>
and
<menu th:fragment="menu-copy">
<p>menu</p>
<a href="/home2.html"
th:href="@{/home2}">主页面2</a>
<a href="/subscribe"
th:href="@{/subscribe}">订阅点这里</a>
<a href="/"
th:href="@{/}">主页面</a>
</menu>
and
<div>
<p>menu</p>
<a href="/home2.html"
th:href="@{/home2}">主页面2</a>
<a href="/subscribe"
th:href="@{/subscribe}">订阅点这里</a>
<a href="/"
th:href="@{/}">主页面</a>
</div>
它们也有共同的部分:片段表达式
· ~{templatename::selector} :templatename 是 html 文件名,selector 是文件中目标片段的 th:fragment 属性值。
· ~{templatename}:表示复制整个模板。
· ~{::selector} or ~{this::selector} :表示在当前模板中用 selector 匹配目标片段。
注意到 templatename 必须要能够被当前模板引擎使用的模板解析器正确解析 ,否则如果 selector 没有匹配到片段,会报异常,~{} 依然可以被省略。
Both templatename and selector can be fully-featured expressions(even conditionals!):
<div th:insert="menu :: (${page.home}? #{menu.home}:#{menu.common})"></div>
在上面的例子中,主页面会加载一个特有的导航栏,前提是 menu 文件中有该目标片段。
(没有主页面跳转链接的导航栏,本来就不需要φ(* ̄0 ̄))
🙋♀️:直接在主页面的模板中指定用主页面导航栏不就行了?需要判断吗?
🤦♂️:好像也是φ(゜▽゜*)♪,咳咳这就是一个说明可以用条件式的例子罢了(🤷♂️我不管~)
id 属性可以代替 th:fragment ,片段被复制到新的模板中之后也可以引用模板中的变量,there is a example
@GetMapping("/")
public String home(Model model){
String enname = "TCJ";
String cnname = "田超杰";
String nname = null;
boolean admin = true;
model.addAttribute("user",new users(enname,cnname,nname,admin));
model.addAttribute("webs",webs);
return "home";
}
首先,在主页控制器往模型中添加 user 数据
然后,特制 导航栏
<menu id="houtai-copy" th:if="${user.admin}">
<a href="/houtai.html"
th:href="@{/hhhhhhhoutai}">管理员后台</a>
</menu>
最后,在主页模板中进行复制
<div th:insert="menu :: #houtai-copy"></div>
只有管理员访问网站时才会看到 管理员后台 的跳转链接。
为了进一步提高代码的复用性,Thymeleaf 提供了可参数化的片段签名,例如
Java 的方法、C 和 Python 的函数等,那 Thymeleaf 的 “函数” 的语法是什么呢?
构造语法
<!--属性值(Var)-->
<div th:fragment="frag(onevar,twovar)">
<p th:text="${onevar} + 'and' + ${twovar}">...</p>
</div>
和编程语言中的构造语法基本一致。
调用
<div th:insert="templatename::frag(${value1},${value2})">...</div>
<div th:replace="templatename::frag(onevar=${value1},twovar=${value2})">...</div>
第二个语法中变量赋值语句可以乱序,例如
<div th:replace="::frag (twovar=${value2},onevar=${value1})">...</div>
即使在构造的时候没有声明参数,也可以在调用的时候加入
<!--声明-->
<div th:fragment="frag"> ... </div>
<!--调用-->
<div th:replace="::frag(onevar=${value1},towvar=${value2})">...</div>
<!--调用时添加参数的第二种方法-->
<div th:replace="::frag" th:with="onevar=${value1},twovar=${value2}">...</div>
和编程语言不同的是,被调用的片段可以使用该模板的上下文变量,而 Java 只允许使用同一个类中的全局变量和传进来的参数。
现在,扩展一下语法
<!--构造,注意 title 和 links 变量的使用-->
<head th:fragment="common_header(title,links)">
<title th:replace="${title}">The shopping application</title>
<!-- Common styles and scripts -->
<link rel="stylesheet" type="text/css" media="all" th:href="@{/css/shoppingapp.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>Shopping - Main</title>
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
<link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">
</head>
...
运行它并且结果如下:
...
<head>
<title>Shopping - Main</title>
<!-- Common styles and scripts -->
<link rel="stylesheet" type="text/css" media="all" href="/sho/css/shoppingapp.css">
<link rel="shortcut icon" href="/sho/images/favicon.ico">
<script type="text/javascript" src="/sho/sh/scripts/codebase.js"></script>
<link rel="stylesheet" href="/sho/css/bootstrap.min.css">
<link rel="stylesheet" href="/sho/themes/smoothness/jquery-ui.css">
</head>
...
再稍改一下
<head th:replace="base :: common_header(~{::title},~{})">
<title>Shopping - Main</title>
</head>
...
结果是:
...
<head>
<title>Shopping - Main</title>
<!-- Common styles and scripts -->
<link rel="stylesheet" type="text/css" media="all" href="/sho/css/Shoppingapp.css">
<link rel="shortcut icon" href="/sho/images/favicon.ico">
<script type="text/javascript" src="/sho/sh/scripts/codebase.js"></script>
</head>
...
使用默认值的语法 _
...
<head th:replace="base :: common_header(_,~{::link})">
<title>Shopping - Main</title>
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
<link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">
</head>
...
‘_’ results in current part of the fragment not being executed at all( title = no-operation),so the result is:
...
<head>
<title>The shopping application</title>
<!-- Common styles and scripts -->
<link rel="stylesheet" type="text/css" media="all" href="/sho/css/shoppingapp.css">
<link rel="shortcut icon" href="/sho/images/favicon.ico">
<script type="text/javascript" src="/sho/sh/scripts/codebase.js"></script>
<link rel="stylesheet" href="/sho/css/bootstrap.min.css">
<link rel="stylesheet" href="/sho/themes/smoothness/jquery-ui.css">
</head>
...
th:insert 的表达式也可以是conditionals:
...
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : ~{}">...</div>
...
又或者:
...
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : _">...</div>
...
之前学习的
<div th:insert="~{templatename :: selector}"></div>
在 templatename or selector 未被正确解析或匹配到目标片段时会抛出异常,th:assert 属性与此类似,它通过创建一个表达式列表,并且当列表中的值均为 true 或等效与 true 时才会执行代码,否则会抛出异常:
<header th:fragment="contentheader(title)" th:assert="${!#strings.isEmpty(title)}">...</header>
可以用它来检验参数。
除此之外,我们可以通过模板解析器来检查模板资源是否存在,即通过它们的 checkExistence 标志。我们也可以把片段是否存在作为一个默认的条件:
...
<!-- 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>
...
简言之就是若该模板和目标片段都存在,该语法和 th:insert=“~{templatename :: selector}” 基本一样;如果不存在,返回 null ,它不会报异常。
模板继承
把 th:fragment 属性移到 <html> 标签中完成构造,then 在另一个模板的 <html> 标签中加入 th:replace 属性完成调用,切不可用 th:insert 否则会造成双重 <html> 标签。
iv th:insert=“~{common :: salutation} ?: _”>
Welcome [[${user.name}]], click here for help-desk support.
简言之就是若该模板和目标片段都存在,该语法和 th:insert=“~{templatename :: selector}” 基本一样;如果不存在,返回 null ,它不会报异常。
模板继承
把 th:fragment 属性移到 <html> 标签中完成构造,then 在另一个模板的 <html> 标签中加入 th:replace 属性完成调用,切不可用 th:insert, 否则会造成双重 <html> 标签。
— 【参考资料 —— Thymeleaf文档20181029 - 29 October 2018】
已同步更新至个人博客:田超杰的个人网站-一个传播计算机知识和人生哲理的博客