Rob Winch
Rob Winch

Reputation: 21720

How to add additional content to fragment in Thymeleaf 3

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

Answers (1)

Daniel Fern&#225;ndez
Daniel Fern&#225;ndez

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

Related Questions