Thymeleaf系列【6】模板布局

模板布局

包含模板片段

模板就是公用资源,可以多次重复使用的内容。经常把页眉,页脚,菜单做成模板,在各个其他页面使用。

模板需要先定义再使用,可以在当前页面定义模板,也可在其他页面中定义模板。

Thymeleaf 需要我们定义这些部分“片段”,以便包含引用,这可以使用th:fragment属性来完成。

假设我们要向index页面添加标准版权页脚,因此创建一个templates/frag/footer.html包含以下代码的文件:

    <div th:fragment="copy">
        <b>&copy; 2021 The Good Thymes Virtual Grocery</b>
    </div>

上面的代码定义了一个名为copy的片段,我们可以使用th:insert或th:replace属性之一轻松地将其包含在我们的主页中(还有th:include,尽管从 Thymeleaf 3.0 开始不再推荐使用它):

在index.html中插入模板片段:

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

也可以使用片段表达式~{…},二者是等效的

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

显示效果:
Thymeleaf系列【6】模板布局

片段规范语法

片段表达式的语法非常简单,共有三种不同的格式:

1. "~{templatename::selector}"
包括在名为 templatename模板上,应用指定的标记选择器所产生的片段。selector可以只是一个片段名称,因此您可以指定~{ templatename::fragmentname},像 ~{footer :: copy}上面那样简单的内容。

标记选择器语法由底层的 AttoParser 解析库定义,类似于 XPath 表达式或 CSS 选择器。

2. "~{templatename}"
包括名为templatename的完整模板。

3." ~ {::selector}“或”~ {this::selector}"
插入来自同一模板的片段,匹配selector,如果在表达式出现的模板上没有找到,模板调用(插入)的堆栈会朝最初处理的模板(根)遍历,直到selector在某个级别匹配。

templatename和selector在上面的例子可以是全功能的表达式(甚至条件语句!),如:

<div th:insert="footer :: (${user.isAdmin}? #{footer.admin} : #{footer.normaluser})"></div>

片段可以包括任何th:*属性。一旦片段被包含到目标模板(带有th:insert/th:replace属性的模板)中,这些属性将被评估,并且它们将能够引用此目标模板中定义的任何上下文变量。

这种处理片段的方法的一大优势是,您可以将片段编写在浏览器可完美显示的页面中,具有完整甚至有效的标记结构,同时仍然保留使 Thymeleaf 将它们包含到其他模板中的能力。

没有 th:fragment标签

当引用模板没有定义th:fragment时,可以使用标记选择器,例如id属性。

定义一个模板,不指定 th:fragment,指定其id属性:

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

使用#加上id属性值,插入模板片段:

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

引用模板

  1. 把模板插入到当前位置
<div th:insert=”模板所在文件名称::模板名称"> </div>
  1. 把模板替换当前标签内容
<div th:replace=”模板所在文件名称::模板名称”>其他内容</div>
  1. 模板内容添加到当前标签,不推荐使用
<div th:include=”模板所在文件名称::模板名称">其他内容</div>

模板引用语法:

  • 模板所在文件名称::模板名称
  • ~{模板所在文件名称::模板名称}

th:insert和th:replace(和th:include,从 3.0 开始不推荐)有什么区别?

  • th:insert 是最简单的:它会简单地插入指定的片段作为其宿主标签的主体。

  • th:replace实际上用指定的片段替换其主机标记。

  • th:include与 类似th:insert,但它不插入片段,而是仅插入此片段的内容。

可参数化的片段

模板作为函数形式使用:

为了为模板片段创建更类似于函数的机制,定义的片段th:fragment可以指定一组参数,然后使用该参数,在引用模板时可以指定参数。

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

这需要使用以下两种语法之一来从th:insertor调用片段th: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>

没有参数的模板片段

Thymeleaf 提供 th:with 属性声明局部变量,只在当前标签内有效。

<div><span th:with="a1=123,a2='哈哈'"> <span th:text="${a1}"></span></span>局部变量</div>

当片段是在没有参数的情况下定义时,比如以下frag111模板,其中引用了一个变量,但是frag111并没有设置参数变量onevar。

<div th:fragment="frag111">
    <span th:text="${onevar}"></span>
</div>

这时我们可以使用上面指定的第二种语法来调用它们(并且只有第二种):

<div th:replace="::frag111 (onevar=1,twovar=2)">

这将相当于组合th:replace和th:with,

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

请注意:片段的局部变量规范,无论它是否具有参数签名,都不会导致上下文在执行之前被清空。Fragment 仍然可以像当前一样访问在调用模板中使用的每个上下文变量。

th:assert 用于模板内断言

该th:assert属性可以指定一个逗号分隔的表达式列表,这些表达式应该被评估并为每个评估产生 true,否则引发异常。

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

这对于验证片段签名中的参数非常有用:

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

灵活布局

fragment标签的参数可以不是文本、数字、bean 对象……而是当面引入模板的标签。

这样就可以让公用模板使用当前模板的html标签,从而产生非常灵活的模板布局机制。

简单来说,就是替换公用模板中的内容。
案例演示:

  1. 定义一个公用模板,传入title和links参数
<head th:fragment="common_header(title,links)">
    <title th:replace="${title}">The awesome application</title>
    <th:block th:replace="${links}" />
</head>
  1. 使用模板,使用~{::title}的方式传入参数,这个表达式表示,将当前的title和link标签内容,当然参数传递给公用模板。
<head th:replace="msg :: 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>
  1. 可以看到,公用模板中的html标签被替换了。

Thymeleaf系列【6】模板布局

使用空片段

一个特殊的片段表达式,空片段( ~{}),当模板的参数为( ~{})时,将不会替换模板中的内容

<head th:replace="msg :: common_header(~{::title},~{})">
    <title>Awesome - Main</title>
    <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
    <link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">
</head>

Thymeleaf系列【6】模板布局

使用无操作标记

使用no-operation标记"_"当做参数传递,表示使用公用模板中的内容,不进行替换

<head th:replace="msg :: 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参数(common_header片段的第一个参数)是如何设置为 ( _) 的,这会导致这部分片段根本没有被执行:
Thymeleaf系列【6】模板布局

高级条件插入片段

空片段和无操作标记的可用性允许我们以非常简单和优雅的方式执行片段的条件插入。

例如,我们可以这样做,以便仅当用户是管理员时才插入我们的common :: adminhead片段,如果不是,则不插入任何内容(空片段):

... ... 此外,我们可以使用无操作标记,以便仅在满足指定条件时插入片段,但如果不满足条件,则不修改标记:
...
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : _">
    Welcome [[${user.name}]], click <a th:href="@{/support}">here</a> for help-desk support.
</div>
...

此外,如果我们已经配置了模板解析器来检查模板资源是否存在——通过它们的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:remove=”删除范围值”

可选范围值:

  • all :删除包含标签及其所有子项
  • body:不删除包含标签,但删除所有的子项
  • tag :删除包含标签,但不要删除其子项
  • all-but-first:删除第一个子项以外的其它所有子项
  • none :什么都不做。该值对于动态计算有用。null也会被视为none
<div th:remove="all"><span>删除模板,包含标签及其所有子项</span></div>
<div th:remove="body">删除模板<span>不删除包含标签,但删除所有的子项</span></div>
<div th:remove="tag">删除模板<span>但不要删除其子项</span></div>
<div th:remove="all-but-first">删除模板<span>:删除第一个子项以外的其它所有子项<a>第二包含</a></span></div>
<div th:remove="none">删除模板<span>但不要删除其子项<a>第二包含</a></span></div>

该th:remove属性可采取任何Thymeleaf标准表示,只要它返回所允许的字符串值中的一个(all,tag,body,all-but-first或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>

在这种情况下,如果${condition}为假,null将返回,因此不会执行删除。

布局继承

为了能够将单个文件作为布局,可以使用片段。具有title和content使用th:fragmentand的简单布局示例th:replace:

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

此示例声明了一个名为layout的片段,其具有标题和内容作为参数。两者都将在继承它的页面上被下面示例中提供的片段表达式替换。

<!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标签将被layout替换,但在布局中title和content将分别被替换为title和section块。

如果需要,布局可以由几个片段组成,如页眉和页脚。

上一篇:节流防抖


下一篇:SpringBoot整合Thymeleaf