模板片段包含
定义和引用片段
在模板中,经常需要包含其他模板中的部分,例如页脚,页眉,菜单等部分。
为了做到这一点,Thymeleaf需要定义这些要包含的部分“片段”,可以使用 th:fragment 属性来完成。
假设我们要在所有页面中添加标准的版权页脚,因此我们创建一个 /WEB-INF/templates/footer.html 包含以下代码的文件:
© 2011 The Good Thymes Virtual Grocery
面的代码定义了一个片段 copy,我们可以使用 th:insert 或 th:replace 属性之一轻松地将其包含在主页中(也可以使用th:include,尽管从Thymeleaf 3.0开始已不再建议使用它):
...
请注意,th:insert 需要一个片段表达式(~{...}),这是一个包含片段的表达式。但是,在上面的示例中,是一个非复杂的片段表达式,(~{,})包围是完全可选的,因此上面的代码等效于:
...
请注意,th:insert 需要一个片段表达式(~{...}),这是一个包含片段的表达式。但是,在上面的示例中,是一个非复杂的片段表达式,(~{,})包围是完全可选的,因此上面的代码等效于:
...
片段规范语法
片段表达式的语法非常简单。有三种不同的格式:"~{templatename::selector}",包括在名为 templatename 的模板上应用指定的标记选择器所产生的片段。请注意,selector 名称可能仅仅是片段名称,因此您可以使用 ~{templatename::fragmentname}语法,像上面的 ~{footer :: copy}一样指定简单的名称。标记选择器语法由基础的AttoParser解析库定义,并且类似于XPath表达式或CSS选择器。有关更多信息,请参见附录:标签选择器语法。"~{templatename}",包括名为 templatename的完整模板。请注意,在th:insert / th:replace 标记中使用的模板名称必须由模板引擎当前正在使用的模板解析器解析。~{::selector} 或 ~{this::selector},从同一模板插入一个片段,匹配 selector。如果在出现表达式的模板上未找到,则将模板调用(插入)堆栈遍历到原始处理的模板(root),直到 selector在某个级别上匹配为止。
在上面的例子中,templatename 和 selector 都可以是全功能的表达式(甚至条件语句!),如:
再次注意 ~{...} 包围在 th:insert/th:replace 中是可选的。
片段可以包含任何 th:*属性。一旦将片段包含到目标模板(带有 th:insert/ th:replace属性的片段)中,就会评估这些属性,并且它们将能够引用此目标模板中定义的任何上下文变量。这种片段处理方法的一大优势是,可以将片段写在浏览器可以完美显示的页面中,并具有完整甚至有效的标记结构,同时仍保留使Thymeleaf将其包含在其他模板中的功能。
不使用th:fragment引用片段
借助标记选择器的强大功能,我们可以包含任何不使用 th:fragment属性的片段。甚至可能是完全不了解Thymeleaf的来自不同应用程序的标记代码:
...
© 2011 The Good Thymes Virtual Grocery
...
我们可以使用上面的片段,简单地通过其 id属性引用它,类似于CSS选择器:
...
th:insert、th:replace、th:include之间的差异
th:insert、th:replace、th:include(3.0之后不推荐使用)之间有何区别?th:insert 最简单,它简单地将指定的片段作为其host标签的主体插入。
th:replace实际上将其标签替换为指定的片段。
th:include 与 th:insert 相似 ,但不插入片段,而是仅插入该片段的内容。
HTML片段如下所示:
© 2011 The Good Thymes Virtual Grocery
分别在
...
其结果为:
...
© 2011 The Good Thymes Virtual Grocery
© 2011 The Good Thymes Virtual Grocery
© 2011 The Good Thymes Virtual Grocery
可参数化的片段签名
为了为模板片段创建更类似于函数的机制,使用 th:fragment 定义的片段可以指定一组参数:
...
这需要使用th:insert、th:replace两种语法之一来调用片段:
请注意,在最后一个选项中顺序并不重要:
不带片段参数的片段局部变量
即使片段没有这样的参数定义:
...
我们可以使用上面指定的第二种语法来调用它们(并且只能用第二种):
这将相当于组合 th:replace 和 th:with:
请注意,这种对片段的局部变量的规范(无论它是否具有参数签名)都不会导致上下文在执行之前被清空。片段仍将能够像当前一样访问在调用模板中使用的每个上下文变量。
th:assert模板内断言
th:assert属性可以指定一个逗号分隔的表达式列表,应对其进行评估且每次评估都应为 true,否则将引发异常。
这对于验证片段签名中的参数非常有用:
...
灵活的布局:不仅仅是片段插入
借助片段表达式,我们可以为片段指定参数,这些参数不是文本,数字,bean对象……而是标记片段。
这允许我们以一种方式来创建我们的片段,以便可以使用来自调用模板的标记来丰富它们,从而产生非常灵活的模板布局机制。
请注意以下片段中 title 和 links 变量的使用:
The awesome application现在,我们可以将该片段称为:
...
Awesome - Main...
结果将使用调用模板中的实际
和 标记作为 title 和 links 变量的值,从而导致我们的片段在插入过程中被自定义:...
Awesome - Main...
使用空片段
一个特殊的片段表达式,空片段(~{}),可用于指定无标记。使用前面的示例:
Awesome - Main...
请注意片段(links)的第二个参数如何设置为空片段,因此没有为 块编写任何内容:
...
Awesome - Main...
使用无操作令牌
如果我们只想让我们的片段使用其当前标记作为默认值,那么no-op也可以用作片段的参数。再次使用common_header示例:
...
Awesome - Main...
查看 title 参数(common_header片段的第一个参数)如何设置为no-op(_),这将导致片段的这一部分根本不执行(title= no-operation):
The awesome application结果为:
...
The awesome application...
片段的高级条件插入
空片段和无操作令牌的可用性使我们能够以非常容易且优雅的方式有条件地插入片段。
例如,我们可以做到这一点,只有当用户是管理员才插入 common :: adminhead 片段,如果不是则不插入任何内容(空片段):
...
...
同样,我们可以使用no-operation标记来在仅满足指定条件时插入片段,而在不满足条件的情况下不做任何修改就保留标记:
...
Welcome [[${user.name}]], click here for help-desk support.
...
另外,如果我们已经配置了模板解析器以通过其标志 - 检查模板资源 checkExistence 的存在,我们可以使用片段本身的存在作为默认操作中的条件:
...
Welcome [[${user.name}]], click here for help-desk support.
...
删除模板片段
看一段示例应用程序:
NAME | PRICE | IN STOCK | COMMENTS |
---|---|---|---|
Onions | 2.41 | yes | 2 comment/s th:href="@{/product/comments(prodId=${prod.id})}" th:unless="${#lists.isEmpty(prod.comments)}">view |
这段代码作为模板很好用,但是作为静态页面(由浏览器直接打开而不由Thymeleaf处理)将不能成为一个好的原型。
为什么?因为尽管该表格可被浏览器完美显示,但该表仅有一行,并且该行仅有模拟数据。作为原型,它看起来根本不够现实……我们应该有多个产品,我们需要更多行。
因此,我们添加一些:
NAME | PRICE | IN STOCK | COMMENTS |
---|---|---|---|
Onions | 2.41 | yes | 2 comment/s th:href="@{/product/comments(prodId=${prod.id})}" th:unless="${#lists.isEmpty(prod.comments)}">view |
Blue Lettuce | 9.55 | no | 0 comment/s |
Mild Cinnamon | 1.99 | yes | 3 comment/s |
好的,现在我们有三个,对于原型来说绝对更好。但是……当我们用Thymeleaf处理它时会发生什么?:
NAME | PRICE | IN STOCK | COMMENTS |
---|---|---|---|
Fresh Sweet Basil | 4.99 | yes | 0 comment/s |
Italian Tomato | 1.25 | no | 2 comment/s |
Yellow Bell Pepper | 2.50 | yes | 0 comment/s |
Old Cheddar | 18.75 | yes | 1 comment/s |
Blue Lettuce | 9.55 | no | 0 comment/s |
Mild Cinnamon | 1.99 | yes | 3 comment/s |
最后两行是模拟行!好吧,原因当然是:迭代仅应用于第一行,而Thymeleaf也没有理由删除其它两行。
我们需要一种在模板处理期间删除这两行的方法。让我们 th:remove 在第二个和第三个
标记上使用该属性:NAME | PRICE | IN STOCK | COMMENTS |
---|---|---|---|
Onions | 2.41 | yes | 2 comment/s th:href="@{/product/comments(prodId=${prod.id})}" th:unless="${#lists.isEmpty(prod.comments)}">view |
Blue Lettuce | 9.55 | no | 0 comment/s |
Mild Cinnamon | 1.99 | yes | 3 comment/s |
处理后,内容将重新显示为:
NAME | PRICE | IN STOCK | COMMENTS |
---|---|---|---|
Fresh Sweet Basil | 4.99 | yes | 0 comment/s |
Italian Tomato | 1.25 | no | 2 comment/s |
Yellow Bell Pepper | 2.50 | yes | 0 comment/s |
Old Cheddar | 18.75 | yes | 1 comment/s |
all 该属性中的值是什么意思?th:remove 可以根据其值以五种不同的方式表现:all:删除包含标签及其所有子标签。
body:请勿删除包含标签,而是删除其所有子标签。
tag:删除包含的标签,但不要删除其子级。
all-but-first:除去第一个标签以外的所有包含标签的子标签。
none: 什么也不做。该值对于动态评估很有用。
all-but-first值有什么用?这将使我们 th:remove="all" 在制作原型时节省一些:
NAMEPRICEIN STOCKCOMMENTS
Onions2.41yes2 comment/s
th:href="@{/product/comments(prodId=${prod.id})}"
th:unless="${#lists.isEmpty(prod.comments)}">view
Blue Lettuce9.55no0 comment/s
Mild Cinnamon1.99yes3 comment/s
th:remove 属性可采取任何Thymeleaf标准表示,只要它返回所允许的字符串值中的一个(all,tag,body,all-but-first或 none)。
这意味着删除可能是有条件的,例如:
还要注意,th:remove考虑 null 的同义词 none,因此以下内容与上面的示例相同:
在这种情况下,如果 ${condition} 为 false,null将被返回,因此不会执行删除。
布局继承
为了能够将单个文件作为布局,可以使用片段。具有 title 和 content 使用 th:fragment 和的简单布局的示例th:replace:
Layout TitleLayout H1
Layout content
Layout footer
此示例声明一个名为 layout的片段,其中标题和内容为参数。在下面的示例中,这两者都将在页面上被继承的片段表达式替换,并继承它。
Page TitlePage content
在这个文件中,该 html 标签将被替换的布局,但在布局 title 和 content 将已被替换 title,并 section分别块。
如果需要,布局可以由几个片段组成,例如 header 和 footer。
附录:标签选择器语法
Thymeleaf的标记选择器直接从Thymeleaf的解析库:AttoParser中借用。
此选择器的语法与XPath,CSS和jQuery中的选择器具有很大的相似性,这使得它们易于用于大多数用户。
例如,以下选择器将在标记内的每个位置选择每个class为 content的
基本语法包括:/x表示名称为x的当前节点的直接子节点。
//x表示任何深度的名称为x的当前节点的子节点。
x[@z="v"] 表示名称为x的元素和名为z的属性,值为“v”。
x[@z1="v1" and @z2="v2"] 表示名称为x的元素,属性z1和z2分别为值“v1”和“v2”。
x[i] 表示名称为x的元素,位于其兄弟姐妹中的数字i中。
x[@z="v"][i] 表示名称为x的元素,属性z的值为“v”,并且在其兄弟姐妹中的数字i中也与此条件匹配。
但也可以使用更简洁的语法:x完全等价于 //x(x在任何深度级别搜索具有名称或引用的元素,引用是属性 th:ref或 th:fragment属性)。
选择器也允许没有元素名称/引用,只要它们包含参数规范。因此 [@class='oneclass']是一个有效的选择器,它使用带有值的class属性查找任何元素(标记)"oneclass"。
高级属性选择功能:除了 =(相等)之外,其他比较运算符也是有效的:( !=不等于),^=(以...开头)和 $=(以...结尾)。例如:x[@class^='section']表示具有名称的元素 x和以 ... class 开头的属性值 section。
可以从 @(XPath样式)和不带(jQuery样式)开始指定属性。所以 x[z='v']相当于 x[@z='v']。
多属性修饰符既可以与 and(XPath样式)连接,也可以通过链接多个修饰符(jQuery样式)来连接。所以x[@z1='v1' and @z2='v2']实际上相当于x[@z1='v1'][@z2='v2'](也是 x[z1='v1'][z2='v2'])。
类似jQuery的直接选择器:x.oneclass相当于 x[class='oneclass']。
.oneclass 相当于[class='oneclass']。
x#oneid 相当于 x[id='oneid']。
#oneid 相当于 [id='oneid']。
x%oneref 表示 具有 th:ref="oneref"或 th:fragment="oneref"属性的标记。
%oneref表示具有 th:ref="oneref"或 th:fragment="oneref"属性的任何标记。请注意,这实际上等同于简单的oneref,因为可以使用引用而不是元素名称。
直接选择器和属性选择器可以混合使用:a.external[@href^='https']。
所以上面的Markup Selector表达式:
可以写为:
另外一个不同的例子:
将寻找 th:fragment="myfrag" 片段签名(或 th:ref 引用)。但是myfrag如果它们存在的话,它们也会查找带有名称的标签(它们不是HTML格式的标签)。注意区别:
实际上会查找任何元素 class="myfrag",而不关心 th:fragment签名(或 th:ref引用)。
多class匹配
标记选择器将类属性理解为多值,因此即使元素具有多个类值,也允许在此属性上应用选择器。
例如,div.two 将匹配
我是银河架构师,十年饮冰,难凉热血,愿历尽千帆,归来仍是少年!
如果文章对您有帮助,请举起您的小手,轻轻【三连】,这将是笔者持续创作的动力源泉。当然,如果文章有错误,或者您有任何的意见或建议,请留言。感谢您的阅读!文章不定时更新,可微信搜索「银河架构师」,精彩内容,先睹为快!