Reputation: 21720
I'd like to add additional content to a fragment using Thymeleaf 3 layouts, but cannot figure out how to do it. For example, I'd like to have a fragment named layout
that looks like:
<head th:fragment="head(title)">
<title th:include="${title}">My App: </title>
</head>
Then have a template that uses the fragment above using:
<head th:include="layout :: head(title=~{::title})">
<title>Please Login</title>
</head>
The content renders like:
<head>
<title>Please Login</title>
</head>
However, I would like to modify the templates so that it renders like the following and placing My App:
in the layout
template (I don't want to have to duplicate it).
<head>
<title>My App: Please Login</title>
</head>
I can get this to work using the following:
<head th:fragment="head(title)">
<title th:include="${title}">My App: <th:block th:include="${title}"></th:block></title>
</head>
However, the Thymeleaf discourages the use of th:include
. From the reference:
And what is the difference between th:insert and th:replace (and th:include, not recommended since 3.0)?
Can someone tell me how to fix my templates so that it renders as shown above using best practices (As mentioned the reference implies this means not using th:include
)?
Upvotes: 2
Views: 4504
Reputation: 7455
The complexity here comes from the fact that you don't want your <title>
tag to go directly into your fragment (which would be easy with your ~{::title}
and a th:replace
at the fragment's <title>
tag). Instead, here as you explain you are actually enriching your fragment's <title>
with textual content coming from the including template.
The key here would be to use the /text()
modifier in your markup selector, which means "select text contents of this tag", like:
<head th:include="layout :: head(title=~{::title/text()})">
<title>Please Login</title>
</head>
(see http://www.attoparser.org/apidocs/attoparser/2.0.0.RELEASE/org/attoparser/select/package-summary.html for the complete reference of the markup selector syntax)
That would make your title
variable contain a Fragment
object consisting of a single node/event, an IText
containing the text "Please Login"
.
As you mention, th:include
is now discouraged (to be deprecated in 3.1) and instead th:insert
and th:replace
are the preferred options. The reason is th:include
's mechanism seemed to be not completely immediate and commonly provoked misunderstandings (basically, a lot of people thought it did what now th:insert
does, which is much simpler). Besides, th:include
added some unwanted computational complexity.
th:replace
does exactly the same as in 2.1, this is, actually replace the host tag with the fragment. th:insert
will insert the fragment into the body of the host tag. A set of simpler options, IMHO.
Back to your code, I would therefore evolve it towards using th:replace
:
<head th:replace="layout :: head(title=~{::title/text()})">
<title>Please Login</title>
</head>
And as for your fragment, in your case I'd go for inlining, probably the simplest option here:
<head th:fragment="head(title)">
<title>My App: [[${title}]]</title>
</head>
Note that in this case we are using a Fragment
(i.e. the result of a fragment expression, in this case ~{::title/text()}
), and we are simply outputting it via inlining (equivalent to th:text
) as if instead of a fragment the title
variable contained a mere String
. But that's part of the flexibility of the new fragment expressions in v3.0.
If you don't like inlining, you could go for something like:
<head th:fragment="head(title)">
<title th:text="|My App: ${title}|">My App</title>
</head>
And if there is the possibility that the including template has no <title>
tag and you want to check that possibility and just use the My App
text as title if there is no title being sent to the fragment, you could use the also-new no-op token (_
):
<head th:fragment="head(title)">
<title th:text="${title} ? |My App: ${title}| : _">My App</title>
</head>
Disclaimer, per StackOverflow rules: I'm Thymeleaf's project lead.
Upvotes: 6