在我们的模板中,我们经常希望包含其他模板的部分内容,如:页脚、页眉、菜单...等等。
为了做到这一点,Thymeleaf 需要我们定义这些部分模板(即模板 “片段”),以便其他模板引用。Thymeleaf 可以通过 th:fragment 属性来实现片段定义。
假设我们想给所有的杂货店页面添加一个标准的版权页脚,那么我们就创建一个 /WEB-INF/templates/footer.html 文件,其中包含以下代码。
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <body> <div th:fragment="copy"> © 2011 The Good Thymes Virtual Grocery </div> </body> </html>
上面的代码定义了一个名为 “copy” 的片段,我们可以使用 th:insert 或 th:replace 属性(也可以使用 th:include 属性,不过从 Thymeleaf 3.0 开始不再推荐使用该属性)轻松地将其纳入我们的主页模板。代码如下:
<body> ... <div th:insert="~{footer :: copy}"></div> </body>
注意,th:insert 属性期望一个片段表达式(~{...}),它是一个能生成片段的表达式。不过在上面的例子中,这是一个非复杂的片段表达式,使用 “~{” 和符号 “}” 包围片段名是完全可选的,所以上面的代码将等同于下面代码:
<body> ... <div th:insert="footer :: copy"></div> </body>
注意:如果在 /view/footer.html 页面定义了片段 copy,那么在页面 /home.html 或 /view/home.html 中需要使用 th:insert="~{view/footer :: copy}" 或者 th:insert="view/footer :: copy"。
片段表达式的语法非常简单明了。有三种不同的格式。
(1)“~{templename :: selector}” 表达式将包含在名为 templename 的模板上使用 th:fragment 属性定义的名为 selector 的片段。注意,selector 可以只是一个片断名称,因此您可以指定类似于上面的 ~{footer :: copy} 这样简单的名称,如:~{templatename :: fragmentname}。
(2)“~{templatename}” 表达式可以仅仅包括名为 templatename 的完整模板,整个模板 body 标签内部的元素全部引用过来。
(3)“~{::selector}” 或 “~{this::selector}” 从同一模板插入匹配 selector 的片段。如果在出现片段表达式的模板上匹配不到 selector,模板调用堆栈将向最初处理的模板(根)遍历,直到选择器在某个级别匹配。例如:
fragment3.html 内容如下:
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org/"> <head> <meta charset="UTF-8"> <title>Thymeleaf模板引擎</title> </head> <body> <div style="display:none;"> <!-- 定义一个名为 myCopy 的片段 --> <div th:fragment="myCopy"> <p>Copyright ©2022 版权信息</p> </div> </div> <!-- 引用一个完整的模板 --> <div th:insert="~{view/public3 :: copy}">Copyright Info</div> </body> </html>
public3.html 内容如下:
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org/"> <body> <div th:fragment="copy"> <!-- 引用了一个当前模板文件没有定义的片段,但是在父模板 fragment3.html 中定义了它 --> <div th:insert="~{this :: myCopy}">MyCopy Info</div> </div> </body> </html>
注意,上述示例中的 templatename 和 selector 都可以是功能齐全的表达式(甚至是条件表达式),如:
<div th:insert="footer :: (${user.isAdmin}? #{footer.admin} : #{footer.normaluser})"></div>
再次提示,片段表达式两边的花括号 “~{…}” 在 th:insert 或 th:replace 属性中是可选的。
片段可以包含任何 th:* 属性。一旦片段被包含到目标模板(具有 th:insert / th:replace 属性的模板)中,这些属性将被求值,并且它们将能够引用此目标模板中定义的任何上下文变量。
由于标记选择器的强大功能,我们可以包含不使用任何 th:fragment 属性的片段。它甚至可以是来自完全不了解 Thymeleaf 的不同应用程序的标记代码:
... <div id="copy-section"> © 2011 The Good Thymes Virtual Grocery </div> ...
我们可以使用上面的片段,只需通过其 id 属性引用它,引用方法与 CSS 选择器类似:
<body> ... <div th:insert="~{footer :: #copy-section}"></div> </body>
th:insert 和 th:replace(以及 th:include,自 Thymeleaf 3.0 以来不推荐)属性之间有什么区别?
th:insert 是最简单的,它将简单地插入指定的片段作为其主标记的主体(保留主标记)。
th:replace 实际上用指定的片段替换其主标记。
th:include 类似于 th:insert,但它只插入该片段的内容,而不是插入片段标签。
因此,HTML片段如下:
<footer th:fragment="copy"> © 2011 The Good Thymes Virtual Grocery </footer>
在模板中分别使用 th:insert、th:replace 和 th:include 属性引用片段,如下所示:
<body> ... <div th:insert="footer :: copy"></div> <div th:replace="footer :: copy"></div> <div th:include="footer :: copy"></div> </body>
引擎渲染后,输出结果:
<body> ... <div> <footer> © 2011 The Good Thymes Virtual Grocery </footer> </div> <footer> © 2011 The Good Thymes Virtual Grocery </footer> <div> © 2011 The Good Thymes Virtual Grocery </div> </body>