Thymeleaf series [6] template layout

Posted by The_PHP_Newb on Sun, 16 Jan 2022 23:12:11 +0100

Template layout

Include template fragment

A template is a common resource that can be reused many times. The header, footer and menu are often made into templates for use on other pages.

Templates need to be defined before use. You can define templates on the current page or in other pages.

Thymeleaf requires us to define these partial "fragments" to include references, which can be done using the th:fragment attribute.

Suppose we want to add a standard copyright footer to the index page, so create a templates / frag / footer HTML file containing the following code:

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

The above code defines a fragment called copy. We can easily include it in our home page using one of the th:insert or th:replace attributes (and th:include, although it is no longer recommended from Thymeleaf 3.0):

In index Insert template fragment in HTML:

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

You can also use the fragment expression ~ {...}, which is equivalent

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

Display effect:

Fragment specification syntax

The syntax of fragment expressions is very simple. There are three different formats:

1. "~{templatename::selector}"
Include the fragment generated by applying the specified tag selector on the template named templatename. The selector can be just a fragment name, so you can specify ~ {templatename::fragmentname}, as simple as ~ {footer:: copy} above.

Tag selector syntax is defined by the underlying AttoParser parsing library, similar to XPath expressions or CSS selectors.

2. "~{templatename}"
Include a complete template named templatename.

3. "~ {this::selector}" or "~ {this::selector}"
Inserts the first mock exam from the same template to match selector. If the template is not found on the expression, the stack of template call (insert) will traverse the initial template (root) until selector is matched at a certain level.

templatename and selector in the above examples can be fully functional expressions (even conditional statements!), For example:

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

Fragments can include any th: * attribute. Once the fragment is included in the target template (the template with the th:insert/th:replace attribute), these attributes will be evaluated and they will be able to reference any context variables defined in the target template.

One advantage of this approach to fragments is that you can write fragments in a page that the browser can display perfectly, with a complete or even effective tag structure, while still retaining the ability for Thymeleaf to include them in other templates.

No th:fragment tag

When the reference template does not define a th:fragment, you can use a tag selector, such as the id attribute.

Define a template without specifying th:fragment, and specify its id attribute:

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

Insert the template fragment by # adding the id attribute value:

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

Reference template

  1. Insert template into current location
<div th:insert="Name of the file where the template is located::Template name"> </div>
  1. Replace the template with the current label content
<div th:replace="Name of the file where the template is located::Template name>Other content</div>
  1. The template content is added to the current tag, which is not recommended
<div th:include="Name of the file where the template is located::Template name">Other content</div>

Template reference syntax:

  • File name of template: template name
  • ~{file name of template:: template name}

What is the difference between th:insert and th:replace (and th:include, not recommended since 3.0)?

  • th:insert is the simplest: it simply inserts the specified fragment as the body of its host tag.

  • th:replace actually replaces its host tag with the specified fragment.

  • th:include is similar to th:insert, but it does not insert a fragment, but only the contents of the fragment.

Parameterizable fragment

Templates are used as functions:

In order to create a more function like mechanism for template fragments, the defined fragment th:fragment can specify a set of parameters, which can be used to specify parameters when referring to the template.

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

This requires one of the following two syntax to call fragment th:replace from th: inserter:

<div th:replace="::frag (${value1},${value2})">...</div>
<div th:replace="::frag (onevar=${value1},twovar=${value2})">...</div>

Note that the order in the last option is not important:

<div th:replace="::frag (twovar=${value2},onevar=${value1})">...</div>

Template fragment without parameters

Thymeleaf provides the th:with attribute to declare local variables, which are only valid in the current tag.

<div><span th:with="a1=123,a2='ha-ha'"> <span th:text="${a1}"></span></span>local variable</div>

When the fragment is defined without parameters, for example, the following frag111 template refers to a variable, but frag111 does not set the parameter variable onevar.

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

At this time, we can use the second syntax specified above to call them (and only the second):

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

This would be equivalent to combining th:replace and th:with,

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

Note that the fragment's local variable specification, whether it has a parameter signature or not, does not cause the context to be cleared before execution. Fragment can still access each context variable used in the call template as it currently does.

th:assert is used for assertions within the template

The th:assert attribute can specify a comma separated list of expressions that should be evaluated and generate true for each evaluation, otherwise an exception will be thrown.

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

This is useful for verifying parameters in a fragment signature:

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

Flexible layout

The parameters of the fragment tag can not be text, number, bean object... But the tag introduced into the template face to face.

In this way, the public template can use the html tag of the current template, resulting in a very flexible template layout mechanism.

Simply put, it is to replace the content in the common template.
Case presentation:

  1. Define a common template and pass in the title and links parameters
<head th:fragment="common_header(title,links)">
    <title th:replace="${title}">The awesome application</title>
    <th:block th:replace="${links}" />
</head>
  1. Use the template and pass in the parameters in the way of ~ {:: Title}. This expression means that the current title and link tag contents, of course, parameters, are passed to the public template.
<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. As you can see, the html tag in the common template has been replaced.

Use empty clips

A special fragment expression, empty fragment (~ {}). When the parameter of the template is (~ {}), the content in the template will not be replaced

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

Use no action flag

Use no operation tag "" Passed as a parameter, it means that the content in the common template is used and will not be replaced

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

See how the title parameter (the first parameter of the common_header fragment) is set to () , which will cause this part of the fragment not to be executed at all:

Advanced conditional insert clip

The availability of empty fragments and no operation tags allows us to perform conditional insertion of fragments in a very simple and elegant way.

For example, we can do this so that our common:: adminhead fragment is inserted only when the user is an administrator, and if not, nothing is inserted (empty fragment):

...

...
... In addition, we can use the no action tag to insert the clip only when the specified conditions are met, but if the conditions are not met, the tag is not modified:
...
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : _">
    Welcome [[${user.name}]], click <a th:href="@{/support}">here</a> for help-desk support.
</div>
...

In addition, if we have configured the template parser to check whether the template resources exist - through their checkExistence flag - we can use the existence of the fragment itself as the condition in the default operation:

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

Delete template

Syntax: th:remove = "delete range value"

Optional range value:

  • All: delete the containing label and all its children
  • body: do not delete the containing tag, but delete all children
  • Tag: delete the containing tag, but do not delete its children
  • All but first: delete all children except the first one
  • None: do nothing. This value is useful for dynamic calculations. null will also be treated as none
<div th:remove="all"><span>Delete template,Contains the label and all its children</span></div>
<div th:remove="body">Delete template<span>Do not delete the containing label, but delete all children</span></div>
<div th:remove="tag">Delete template<span>But do not delete its children</span></div>
<div th:remove="all-but-first">Delete template<span>:Delete all children except the first one<a>Second contains</a></span></div>
<div th:remove="none">Delete template<span>But do not delete its children<a>Second contains</a></span></div>

The th:remove attribute can take any Thymeleaf standard representation as long as it returns one of the allowed string values (all, tag, body, all but first or none).

This means that removal may be conditional, for example:

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

Also note that th:remove is treated as a synonym for null none, so the following is the same as the above example:

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

In this case, if ${condition} is false, null will be returned, so the deletion will not be performed.

Layout inheritance

To be able to use a single file as a layout, you can use clips. Simple layout example with title and 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>

This example declares a fragment called layout with a title and content as parameters. Both will be replaced on the page that inherits it with the fragment expression provided in the following example.

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

In this file, the html tag will be replaced by layout, but in the layout, the title and content will be replaced with title and section blocks, respectively.

If desired, the layout can consist of several segments, such as headers and footers.

Topics: Java Spring Spring Boot Thymeleaf