div css页面布局 模板_可能是最全的Thymeleaf参考手册(八):模板布局

8666976408ad21cf48930f77d8f6c48c.gif

模板片段包含

定义和引用片段

在模板中,经常需要包含其他模板中的部分,例如页脚,页眉,菜单等部分。 为了做到这一点,Thymeleaf需要定义这些要包含的部分“片段”,可以使用 th:fragment 属性来完成。 假设我们要在所有页面中添加标准的版权页脚,因此我们创建一个 /WEB-INF/templates/footer.html 包含以下代码的文件:
<html xmlns:th="http://www.thymeleaf.org">  <body>    <div th:fragment="copy">      © 2011 The Good Thymes Virtual Grocery    div>  body>html>

面的代码定义了一个片段 copy,我们可以使用 th:insertth:replace 属性之一轻松地将其包含在主页中(也可以使用th:include,尽管从Thymeleaf 3.0开始已不再建议使用它):

<body>  ...  <div th:insert="~{footer :: copy}">div>body>

请注意,th:insert 需要一个片段表达式(~{...}),这是一个包含片段的表达式。但是,在上面的示例中,是一个非复杂的片段表达式,(~{,})包围是完全可选的,因此上面的代码等效于:

<body>  ...  <div th:insert="footer :: copy">div>body>

片段规范语法

片段表达式的语法非常简单。有三种不同的格式:
  • "~{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的来自不同应用程序的标记代码:

...<div id="copy-section">  © 2011 The Good Thymes Virtual Grocerydiv>...

我们可以使用上面的片段,简单地通过其 id 属性引用它,类似于CSS选择器:

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

th:insert、th:replace、th:include之间的差异

th:insertth:replaceth:include(3.0之后不推荐使用)之间有何区别?
  • th:insert 最简单,它简单地将指定的片段作为其host标签的主体插入。

  • th:replace 实际上将其标签替换为指定的片段。

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

HTML片段如下所示:

<footer th:fragment="copy">  © 2011 The Good Thymes Virtual Groceryfooter>

分别在 

 标签中包含3次,如下所示:
<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>

可参数化的片段签名

为了为模板片段创建更类似于函数的机制,使用 th:fragment 定义的片段可以指定一组参数:
<div th:fragment="frag (onevar,twovar)">    <p th:text="${onevar} + ' - ' + ${twovar}">...p>div>

这需要使用th:insertth:replace两种语法之一来调用片段:

"::frag (${value1},${value2})">...
"::frag (onevar=${value1},twovar=${value2})">...
请注意,在最后一个选项中顺序并不重要:
"::frag (twovar=${value2},onevar=${value1})">...

不带片段参数的片段局部变量

即使片段没有这样的参数定义:
<div th:fragment="frag">    ...div>
我们可以使用上面指定的第二种语法来调用它们(并且只能用第二种):
"::frag (onevar=${value1},twovar=${value2})">这将相当于组合 th:replaceth:with
"::frag" th:with="onevar=${value1},twovar=${value2}">请注意,这种对片段的局部变量的规范(无论它是否具有参数签名)都不会导致上下文在执行之前被清空。片段仍将能够像当前一样访问在调用模板中使用的每个上下文变量。

th:assert模板内断言

th:assert 属性可以指定一个逗号分隔的表达式列表,应对其进行评估且每次评估都应为 true,否则将引发异常。
"${onevar},(${twovar} != 43)">...
这对于验证片段签名中的参数非常有用:
<header th:fragment="contentheader(title)" th:assert="${!#strings.isEmpty(title)}">...header>

灵活的布局:不仅仅是片段插入

借助片段表达式,我们可以为片段指定参数,这些参数不是文本,数字,bean对象……而是标记片段。这允许我们以一种方式来创建我们的片段,以便可以使用来自调用模板的标记来丰富它们,从而产生非常灵活的模板布局机制。请注意以下片段中 titlelinks 变量的使用:
<head th:fragment="common_header(title,links)">  <title th:replace="${title}">The awesome applicationtitle>    <link rel="stylesheet" type="text/css" media="all" th:href="@{/css/awesomeapp.css}">  <link rel="shortcut icon" th:href="@{/images/favicon.ico}">  <script type="text/javascript" th:src="@{/sh/scripts/codebase.js}">script>    <th:block th:replace="${links}" />head>
现在,我们可以将该片段称为:
...<head th:replace="base :: common_header(~{::title},~{::link})">  <title>Awesome - Maintitle>  <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">  <link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">head>...

结果将使用调用模板中的实际 标记作为 titlelinks 变量的值,从而导致我们的片段在插入过程中被自定义:

...<head>  <title>Awesome - Maintitle>    <link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css">  <link rel="shortcut icon" href="/awe/images/favicon.ico">  <script type="text/javascript" src="/awe/sh/scripts/codebase.js">script>  <link rel="stylesheet" href="/awe/css/bootstrap.min.css">  <link rel="stylesheet" href="/awe/themes/smoothness/jquery-ui.css">head>...

使用空片段

一个特殊的片段表达式,空片段(~{}),可用于指定无标记。使用前面的示例:
<head th:replace="base :: common_header(~{::title},~{})">  <title>Awesome - Maintitle>head>...

请注意片段(links)的第二个参数如何设置为空片段,因此没有为 块编写任何内容:

...<head>  <title>Awesome - Maintitle>    <link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css">  <link rel="shortcut icon" href="/awe/images/favicon.ico">  <script type="text/javascript" src="/awe/sh/scripts/codebase.js">script>head>...

使用无操作令牌

如果我们只想让我们的片段使用其当前标记作为默认值,那么no-op也可以用作片段的参数。再次使用common_header 示例:
...<head th:replace="base :: common_header(_,~{::link})">  <title>Awesome - Maintitle>  <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">  <link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">head>...

查看 title 参数(common_header 片段的第一个参数)如何设置为no-op(_),这将导致片段的这一部分根本不执行(title= no-operation):

<title th:replace="${title}">The awesome applicationtitle>

结果为:

...<head>  <title>The awesome applicationtitle>    <link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css">  <link rel="shortcut icon" href="/awe/images/favicon.ico">  <script type="text/javascript" src="/awe/sh/scripts/codebase.js">script>  <link rel="stylesheet" href="/awe/css/bootstrap.min.css">  <link rel="stylesheet" href="/awe/themes/smoothness/jquery-ui.css">head>...

片段的高级条件插入

空片段和无操作令牌的可用性使我们能够以非常容易且优雅的方式有条件地插入片段。例如,我们可以做到这一点,只有当用户是管理员才插入 common :: adminhead 片段,如果不是则不插入任何内容(空片段):
...<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : ~{}">...div>...

同样,我们可以使用no-operation标记来在仅满足指定条件时插入片段,而在不满足条件的情况下不做任何修改就保留标记:

...
"${user.isAdmin()} ? ~{common :: adminhead} : _"> Welcome [[${user.name}]], click "@{/support}">here for help-desk support.
...

另外,如果我们已经配置了模板解析器以通过其标志 - 检查模板资源 checkExistence 的存在,我们可以使用片段本身的存在作为默认操作中的条件:

...<div th:insert="~{common :: salutation} ?: _">    Welcome [[${user.name}]], click <a th:href="@{/support}">herea> for help-desk support.div>...

删除模板片段

看一段示例应用程序:
<table>  <tr>    <th>NAMEth>    <th>PRICEth>    <th>IN STOCKth>    <th>COMMENTSth>  tr>  <tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">    <td th:text="${prod.name}">Onionstd>    <td th:text="${prod.price}">2.41td>    <td th:text="${prod.inStock}? #{true} : #{false}">yestd>    <td>      <span th:text="${#lists.size(prod.comments)}">2span> comment/s      <a href="comments.html"          th:href="@{/product/comments(prodId=${prod.id})}"  th:unless="${#lists.isEmpty(prod.comments)}">viewa>    td>  tr>table>
这段代码作为模板很好用,但是作为静态页面(由浏览器直接打开而不由Thymeleaf处理)将不能成为一个好的原型。

为什么?因为尽管该表格可被浏览器完美显示,但该表仅有一行,并且该行仅有模拟数据。作为原型,它看起来根本不够现实……我们应该有多个产品,我们需要更多行。

因此,我们添加一些:

<table>  <tr>    <th>NAMEth>    <th>PRICEth>    <th>IN STOCKth>    <th>COMMENTSth>  tr>  <tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">    <td th:text="${prod.name}">Onionstd>    <td th:text="${prod.price}">2.41td>    <td th:text="${prod.inStock}? #{true} : #{false}">yestd>    <td>      <span th:text="${#lists.size(prod.comments)}">2span> comment/s      <a href="comments.html"          th:href="@{/product/comments(prodId=${prod.id})}"  th:unless="${#lists.isEmpty(prod.comments)}">viewa>    td>  tr>  <tr class="odd">    <td>Blue Lettucetd>    <td>9.55td>    <td>notd>    <td>      <span>0span> comment/s    td>  tr>  <tr>    <td>Mild Cinnamontd>    <td>1.99td>    <td>yestd>    <td>      <span>3span> comment/s      <a href="comments.html">viewa>    td>  tr>table>
好的,现在我们有三个,对于原型来说绝对更好。但是……当我们用Thymeleaf处理它时会发生什么?:
<table>  <tr>    <th>NAMEth>    <th>PRICEth>    <th>IN STOCKth>    <th>COMMENTSth>  tr>  <tr>    <td>Fresh Sweet Basiltd>    <td>4.99td>    <td>yestd>    <td>      <span>0span> comment/s    td>  tr>  <tr class="odd">    <td>Italian Tomatotd>    <td>1.25td>    <td>notd>    <td>      <span>2span> comment/s      <a href="/gtvg/product/comments?prodId=2">viewa>    td>  tr>  <tr>    <td>Yellow Bell Peppertd>    <td>2.50td>    <td>yestd>    <td>      <span>0span> comment/s    td>  tr>  <tr class="odd">    <td>Old Cheddartd>    <td>18.75td>    <td>yestd>    <td>      <span>1span> comment/s      <a href="/gtvg/product/comments?prodId=4">viewa>    td>  tr>  <tr class="odd">    <td>Blue Lettucetd>    <td>9.55td>    <td>notd>    <td>      <span>0span> comment/s    td>  tr>  <tr>    <td>Mild Cinnamontd>    <td>1.99td>    <td>yestd>    <td>      <span>3span> comment/s      <a href="comments.html">viewa>    td>  tr>table>
最后两行是模拟行!好吧,原因当然是:迭代仅应用于第一行,而Thymeleaf也没有理由删除其它两行。我们需要一种在模板处理期间删除这两行的方法。让我们 th:remove 在第二个和第三个  标记上使用该属性:
<table> <tr> <th>NAMEth> <th>PRICEth> <th>IN STOCKth> <th>COMMENTSth> tr> <tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'"> <td th:text="${prod.name}">Onionstd> <td th:text="${prod.price}">2.41td> <td th:text="${prod.inStock}? #{true} : #{false}">yestd> <td> <span th:text="${#lists.size(prod.comments)}">2span> comment/s <a href="comments.html"  th:href="@{/product/comments(prodId=${prod.id})}"  th:unless="${#lists.isEmpty(prod.comments)}">viewa> td> tr> <tr class="odd" th:remove="all"> <td>Blue Lettucetd> <td>9.55td> <td>notd> <td> <span>0span> comment/s td> tr> <tr th:remove="all"> <td>Mild Cinnamontd> <td>1.99td> <td>yestd> <td> <span>3span> comment/s <a href="comments.html">viewa> td> tr>table>
处理后,内容将重新显示为:
<table> <tr> <th>NAMEth> <th>PRICEth> <th>IN STOCKth> <th>COMMENTSth> tr> <tr> <td>Fresh Sweet Basiltd> <td>4.99td> <td>yestd> <td> <span>0span> comment/s td> tr> <tr class="odd"> <td>Italian Tomatotd> <td>1.25td> <td>notd> <td> <span>2span> comment/s <a href="/gtvg/product/comments?prodId=2">viewa> td> tr> <tr> <td>Yellow Bell Peppertd> <td>2.50td> <td>yestd> <td> <span>0span> comment/s td> tr> <tr class="odd"> <td>Old Cheddartd> <td>18.75td> <td>yestd> <td> <span>1span> comment/s <a href="/gtvg/product/comments?prodId=4">viewa> td> tr>table>
all 该属性中的值是什么意思?th:remove 可以根据其值以五种不同的方式表现: all:删除包含标签及其所有子标签。body:请勿删除包含标签,而是删除其所有子标签。tag:删除包含的标签,但不要删除其子级。all-but-first:除去第一个标签以外的所有包含标签的子标签。none:什么也不做。该值对于动态评估很有用。all-but-first 值有什么用?这将使我们 th:remove="all" 在制作原型时节省一些:
<table> <thead> <tr> <th>NAMEth> <th>PRICEth> <th>IN STOCKth> <th>COMMENTSth> tr> thead> <tbody th:remove="all-but-first"> <tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'"> <td th:text="${prod.name}">Onionstd> <td th:text="${prod.price}">2.41td> <td th:text="${prod.inStock}? #{true} : #{false}">yestd> <td> <span th:text="${#lists.size(prod.comments)}">2span> comment/s <a href="comments.html"  th:href="@{/product/comments(prodId=${prod.id})}"  th:unless="${#lists.isEmpty(prod.comments)}">viewa> td> tr> <tr class="odd"> <td>Blue Lettucetd> <td>9.55td> <td>notd> <td> <span>0span> comment/s td> tr> <tr> <td>Mild Cinnamontd> <td>1.99td> <td>yestd> <td> <span>3span> comment/s <a href="comments.html">viewa> td> tr> tbody>table>
th:remove 属性可采取任何Thymeleaf标准表示,只要它返回所允许的字符串值中的一个(alltagbodyall-but-first 或 none)。这意味着删除可能是有条件的,例如:
<a href="/something" th:remove="${condition}? tag : none">Link text not to be removeda>

还要注意,th:remove 考虑 null 的同义词 none,因此以下内容与上面的示例相同:

<a href="/something" th:remove="${condition}? tag">Link text not to be removeda>

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

布局继承

为了能够将单个文件作为布局,可以使用片段。具有 titlecontent 使用 th:fragment 和的简单布局的示例th:replace
<html th:fragment="layout (title, content)" xmlns:th="http://www.thymeleaf.org"><head> <title th:replace="${title}">Layout Titletitle>head><body> <h1>Layout H1h1> <div th:replace="${content}"> <p>Layout contentp> div> <footer> Layout footer footer>body>html>

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

<html th:replace="~{layoutFile :: layout(~{::title}, ~{::section})}"><head> <title>Page Titletitle>head><body><section> <p>Page contentp> <div>Included on pagediv>section>body>html>

在这个文件中,该 html 标签将被替换的布局,但在布局 titlecontent 将已被替换 title,并 section 分别块。

如果需要,布局可以由几个片段组成,例如 headerfooter

附录:标签选择器语法

Thymeleaf的标记选择器直接从Thymeleaf的解析库:AttoParser中借用。此选择器的语法与XPath,CSS和jQuery中的选择器具有很大的相似性,这使得它们易于用于大多数用户。例如,以下选择器将在标记内的每个位置选择每个class为 content 的 
(注意这不是尽可能简洁,请继续阅读以了解原因):
<div th:insert="mytemplate :: //div[@class='content']">...div>

基本语法包括:

/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(在任何深度级别搜索具有名称或引用的元素,引用是属性 th:ref 或 th:fragment 属性)。 选择器也允许没有元素名称/引用,只要它们包含参数规范。因此 [@class='oneclass'] 是一个有效的选择器,它使用带有值的class属性查找任何元素(标记)"oneclass"。高级属性选择功能: 除了 =(相等)之外,其他比较运算符也是有效的:( != 不等于),^=(以...开头)和 $=(以...结尾)。例如:x[@class^='section'] 表示具有名称的元素 和以 ... 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表达式:
<div th:insert="mytemplate :: //div[@class='content']">...div>
可以写为:
<div th:insert="mytemplate :: div.content">...div>
另外一个不同的例子:
<div th:replace="mytemplate :: myfrag">...div>

将寻找 th:fragment="myfrag" 片段签名(或 th:ref 引用)。但是myfrag如果它们存在的话,它们也会查找带有名称的标签(它们不是HTML格式的标签)。注意区别:

<div th:replace="mytemplate :: .myfrag">...div>

实际上会查找任何元素 ,而不关心 th:fragment 签名(或 th:ref 引用)。

多class匹配

标记选择器将类属性理解为多值,因此即使元素具有多个类值,也允许在此属性上应用选择器。例如,div.two 将匹配

0d0d43a39347a82d03c36c557f2f1804.gif

- End -

b85189915cd5fd517d563499c7fec4e5.gif

回复以下关键字,获取更多资源

SpringCloud进阶之路 | Java 基础 | 微服务 | JAVA WEB | JAVA 进阶 | JAVA 面试 | MK 精讲

b85189915cd5fd517d563499c7fec4e5.gif

往期精选

06235590b85545024bc9e408b05bddc7.pnga709d155975995aa6ea3753d0503cb83.png 

可能是最全的Thymeleaf参考手册(一):配置

ribbon,不带这么坑人的!

Spring Cloud进阶之路 | 十二:断路器聚合监控(Turbine)

面试宝典(一):除零问题

重温Java基础(七):位运算符

避坑指南(四):zuul整合断路器监控线程池一直loading

docker进阶之路-基础篇 | 二:protainer安装与使用

集群式Quartz定时任务框架实践

如果喜欢我们的文章

可以关注我们

也可以点击右下角的在看告诉我们

期待与您相遇

2ed910a2fda4034120a9cd47c1dd07ac.png

点“在看”你懂得

5eb9f13ea74aabab52844d6e62226256.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值