模板布局 条件计算 局部变量 8.1Including template fragments Defining and referencing fragments 在我们的模板中,我们经常需要包含其他模板中的部分,页脚,标题,菜单等部分...... 为了做到这一点,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> Fragment specification syntax 片段表达式的语法非常简单。有三种不同的格式: "~{templatename::selector}"包括在名为的模板上应用指定标记选择器而产生的片段templatename。请注意,selector可以仅仅是一个片段的名字,所以你可以指定为简单的东西~{templatename::fragmentname}就像在~{footer :: copy}上面。 标记选择器语法由底层AttoParser解析库定义,类似于XPath表达式或CSS选择器。有关详细信息,请参阅附录C. "~{templatename}"包含名为的完整模板templatename。 请注意,您在th:insert/ th:replacetags中使用的模板名称必须由模板引擎当前使用的模板解析器解析。 ~{::selector}"或"~{this::selector}"插入来自同一模板的片段,进行匹配selector。如果在表达式出现的模板上找不到,则模板调用(插入)的堆栈将遍历最初处理的模板(根),直到selector在某个级别匹配。 双方templatename并selector在上面的例子可以是全功能的表达式(甚至条件语句!),如: <div th:insert="footer :: (${user.isAdmin}? #{footer.admin} : #{footer.normaluser})"></div> 再次注意周围的~{...}包络在th:insert/中是如何可选的th:replace。 片段可以包含任何th:* 属性。一旦将片段包含在目标模板(具有th:insert/ th:replaceattribute的模板)中,就会评估这些属性,并且它们将能够引用此目标模板中定义的任何上下文变量。 这种片段方法的一大优点是,您可以在浏览器完美显示的页面中编写片段,具有完整且有效的标记结构,同时仍保留使Thymeleaf将其包含在其他模板中的能力。 Referencing fragments without th:fragment 由于标记选择器的强大功能,我们可以包含不使用任何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> Difference between th:insert and th:replace (and th:include) 和之间有什么区别th:insert和th:replace(和th:include,因为3.0不推荐)? - th:insert 是最简单的:它只是插入指定的片段作为其主机标签的主体。 - th:replace实际上用指定的片段替换它的主机标签。 th:include类似于th:insert,但不是插入片段,它只插入此片段的内容。 所以像这样的HTML片段: <footer th:fragment="copy"> © 2011 The Good Thymes Virtual Grocery </footer> ...在主机标签中包含三次,如下所示: <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> 8.2Parameterizable fragment signatures 为了为模板片段创建更像函数的机制,使用定义的片段th:fragment可以指定一组参数: <div th:fragment="frag (onevar,twovar)"> <p th:text="${onevar} + ' - ' + ${twovar}">...</p> </div> 这需要使用这两种语法之一来从th:insert或调用片段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> 片段局部变量没有片段参数 即使片段定义没有这样的参数: <div th:fragment="frag"> ... </div> 我们可以使用上面指定的第二种语法来调用它们(只有第二种语法): <div th:replace="::frag (onevar=${value1},twovar=${value2})"> 这将相当于组合th:replace和th:with: <div th:replace="::frag" th:with="onevar=${value1},twovar=${value2}"> 请注意,片段的局部变量规范 - 无论是否具有参数签名 - 都不会导致上下文在执行之前被清空。片段仍然可以像访问目前那样访问调用模板中使用的每个上下文变量。 th:assert for in-template assertions 该th:assert属性可以指定一个以逗号分隔的表达式列表,这些表达式应该被评估并为每次评估生成true,否则会引发异常。 <div th:assert="${onevar},(${twovar} != 43)">...</div> 这对于验证片段签名的参数非常方便: <header th:fragment="contentheader(title)" th:assert="${!#strings.isEmpty(title)}">...</header> 8.3Flexible layouts: beyond mere fragment insertion 由于片段表达式,我们可以为不是文本,数字,bean对象的片段指定参数......而是指定标记片段。 这允许我们以一种方式创建我们的片段,使得它们可以通过来自调用模板的标记来丰富,从而产生非常灵活的模板布局机制。 请注意以下片段中的title和links变量的使用: <head th:fragment="common_header(title,links)"> <title th:replace="${title}">The awesome application</title> <!-- Common styles and scripts --> <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> <!--/* Per-page placeholder for additional links */--> <th:block th:replace="${links}" /> </head> 我们现在可以将这个片段称为: ... <head th:replace="base :: 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> ... ...结果将使用我们的调用模板中的实际和标签作为title和links变量的值,从而导致我们的片段在插入过程中自定义: ... <head> <title>Awesome - Main</title> <!-- Common styles and scripts --> <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> ... Using the empty fragment 一个特殊的片段表达式,即空片段(~{}),可用于指定无标记。使用前面的示例: <head th:replace="base :: common_header(~{::title},~{})"> <title>Awesome - Main</title> </head> ... 注意fragment(links)的第二个参数如何设置为空片段,因此没有为<th:block th:replace="${links}" />块写入任何内容: ... <head> <title>Awesome - Main</title> <!-- Common styles and scripts --> <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> ... Using the no-operation token 如果我们只想让我们的片段使用其当前标记作为默认值,则no-op也可以用作片段的参数。再次,使用common_header示例: ... <head th:replace="base :: 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参数(commonheader片段的第一个参数)设置为no-op( ),这导致片段的这一部分根本不被执行(title= 无操作): <title th:replace="${title}">The awesome application</title> 结果是: ... <head> <title>The awesome application</title> <!-- Common styles and scripts --> <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> ... Advanced conditional insertion of fragments emtpy片段和无操作令牌的可用性允许我们以非常简单和优雅的方式执行片段的条件插入。 例如,我们可以这样做,以便仅在用户是管理员时插入我们的common :: adminhead片段,并且如果不是,则不插入任何内容(emtpy片段): ... <div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : ~{}">...</div> ... 此外,我们可以使用无操作令牌,以便仅在满足指定条件时插入片段,但如果不满足条件则保留标记而不进行修改: ... <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> ... 8.4Removing template fragments 回到示例应用程序,让我们重新访问我们的产品列表模板的最新版本: <table> <tr> <th>NAME</th> <th>PRICE</th> <th>IN STOCK</th> <th>COMMENTS</th> </tr> <tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'"> <td th:text="${prod.name}">Onions</td> <td th:text="${prod.price}">2.41</td> <td th:text="${prod.inStock}? #{true} : #{false}">yes</td> <td> <span th:text="${#lists.size(prod.comments)}">2</span> comment/s <a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}" th:unless="${#lists.isEmpty(prod.comments)}">view</a> </td> </tr> </table> 这段代码作为一个模板很好,但作为一个静态页面(当浏览器直接打开而没有Thymeleaf处理它时)它就不会成为一个好的原型。 为什么?因为虽然浏览器可以完全显示,但该表只有一行,而且这行包含模拟数据。作为原型,它看起来不够逼真......我们应该有多个产品,我们需要更多的行。 所以让我们添加一些: <table> <tr> <th>NAME</th> <th>PRICE</th> <th>IN STOCK</th> <th>COMMENTS</th> </tr> <tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'"> <td th:text="${prod.name}">Onions</td> <td th:text="${prod.price}">2.41</td> <td th:text="${prod.inStock}? #{true} : #{false}">yes</td> <td> <span th:text="${#lists.size(prod.comments)}">2</span> comment/s <a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}" th:unless="${#lists.isEmpty(prod.comments)}">view</a> </td> </tr> <tr class="odd"> <td>Blue Lettuce</td> <td>9.55</td> <td>no</td> <td> <span>0</span> comment/s </td> </tr> <tr> <td>Mild Cinnamon</td> <td>1.99</td> <td>yes</td> <td> <span>3</span> comment/s <a href="comments.html">view</a> </td> </tr> </table> 好的,现在我们有三个,对原型来说肯定更好。但是......当我们用Thymeleaf处理它时会发生什么?: <table> <tr> <th>NAME</th> <th>PRICE</th> <th>IN STOCK</th> <th>COMMENTS</th> </tr> <tr> <td>Fresh Sweet Basil</td> <td>4.99</td> <td>yes</td> <td> <span>0</span> comment/s </td> </tr> <tr class="odd"> <td>Italian Tomato</td> <td>1.25</td> <td>no</td> <td> <span>2</span> comment/s <a href="/gtvg/product/comments?prodId=2">view</a> </td> </tr> <tr> <td>Yellow Bell Pepper</td> <td>2.50</td> <td>yes</td> <td> <span>0</span> comment/s </td> </tr> <tr class="odd"> <td>Old Cheddar</td> <td>18.75</td> <td>yes</td> <td> <span>1</span> comment/s <a href="/gtvg/product/comments?prodId=4">view</a> </td> </tr> <tr class="odd"> <td>Blue Lettuce</td> <td>9.55</td> <td>no</td> <td> <span>0</span> comment/s </td> </tr> <tr> <td>Mild Cinnamon</td> <td>1.99</td> <td>yes</td> <td> <span>3</span> comment/s <a href="comments.html">view</a> </td> </tr> </table> 最后两行是模拟行!嗯,当然它们是:迭代只适用于第一行,所以没有理由为什么Thymeleaf应该删除其他两个。 我们需要一种在模板处理过程中删除这两行的方法。让我们th:remove在第二个和第三个标签上使用该属性: <table> <tr> <th>NAME</th> <th>PRICE</th> <th>IN STOCK</th> <th>COMMENTS</th> </tr> <tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'"> <td th:text="${prod.name}">Onions</td> <td th:text="${prod.price}">2.41</td> <td th:text="${prod.inStock}? #{true} : #{false}">yes</td> <td> <span th:text="${#lists.size(prod.comments)}">2</span> comment/s <a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}" th:unless="${#lists.isEmpty(prod.comments)}">view</a> </td> </tr> <tr class="odd" th:remove="all"> <td>Blue Lettuce</td> <td>9.55</td> <td>no</td> <td> <span>0</span> comment/s </td> </tr> <tr th:remove="all"> <td>Mild Cinnamon</td> <td>1.99</td> <td>yes</td> <td> <span>3</span> comment/s <a href="comments.html">view</a> </td> </tr> </table> 处理完毕后,所有内容都会再次显示: <table> <tr> <th>NAME</th> <th>PRICE</th> <th>IN STOCK</th> <th>COMMENTS</th> </tr> <tr> <td>Fresh Sweet Basil</td> <td>4.99</td> <td>yes</td> <td> <span>0</span> comment/s </td> </tr> <tr class="odd"> <td>Italian Tomato</td> <td>1.25</td> <td>no</td> <td> <span>2</span> comment/s <a href="/gtvg/product/comments?prodId=2">view</a> </td> </tr> <tr> <td>Yellow Bell Pepper</td> <td>2.50</td> <td>yes</td> <td> <span>0</span> comment/s </td> </tr> <tr class="odd"> <td>Old Cheddar</td> <td>18.75</td> <td>yes</td> <td> <span>1</span> comment/s <a href="/gtvg/product/comments?prodId=4">view</a> </td> </tr> </table> all属性中的这个值是什么意思?th:remove可以根据其价值以五种不同的方式表现: all:删除包含标记及其所有子标记。 body:不要删除包含标记,但删除其所有子标记。 tag:删除包含标记,但不删除其子项。 all-but-first:删除除第一个之外的所有包含标记的子项。 none: 没做什么。此值对于动态评估很有用。 这个all-but-first价值有什么用呢?它将让我们th:remove="all"在原型设计时节省一些: <table> <thead> <tr> <th>NAME</th> <th>PRICE</th> <th>IN STOCK</th> <th>COMMENTS</th> </tr> </thead> <tbody th:remove="all-but-first"> <tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'"> <td th:text="${prod.name}">Onions</td> <td th:text="${prod.price}">2.41</td> <td th:text="${prod.inStock}? #{true} : #{false}">yes</td> <td> <span th:text="${#lists.size(prod.comments)}">2</span> comment/s <a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}" th:unless="${#lists.isEmpty(prod.comments)}">view</a> </td> </tr> <tr class="odd"> <td>Blue Lettuce</td> <td>9.55</td> <td>no</td> <td> <span>0</span> comment/s </td> </tr> <tr> <td>Mild Cinnamon</td> <td>1.99</td> <td>yes</td> <td> <span>3</span> comment/s <a href="comments.html">view</a> </td> </tr> </tbody> </table> 该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}为false,null将返回,因此不会执行删除。 8.5Layout Inheritance 为了能够将单个文件作为布局,可以使用片段。具有title和content使用th:fragment和的简单布局的示例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的片段,其中title和content作为参数。两者都将在页面上替换,并在下面的示例中通过提供的片段表达式继承它。 <!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标签将被替换的布局,但在布局title和content将已被替换title,并section分别块。 如果需要,布局可以由多个片段组成页眉和页脚。 条件计算 局部变量