question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[MAJOR FEAT] Fragment Expressions

See original GitHub issue

Building on the idea explained at #180, Thymeleaf 3.0 will introduce a new type of simple expressions for the Thymeleaf Standard Expression engine. These expressions are called Fragment Expressions and their main idea is that they generalize the syntax that was already being used in th:insert/th:replace/th:include in order to be able to use resolved fragments as any other kind of objects in the template execution context.

Their syntax is similar to that of other simple expressions, using the tilde (~) as a selector char. So expressions such as these:

~{commons::main}

…will now not only be usable in th:insert/th:replace/th:include like:

<div th:insert="~{commons :: main}">...</div>

…but also as a part of other more complex expressions like:

<div th:insert="${user.admin}? ~{adm :: admintools} : ~{common :: basictools}">...</div>

…or even:

<div th:with="frag=~{footer :: #main/text()}">
  <p th:insert="${frag}">
</div>

All features available before Thymeleaf 3.0 in fragment insertion syntax are also available in fragment expressions, including:

  • Same-template fragments: ~{::frag}, ~{this::frag}, …
  • Complete-template fragments: ~{main}, ~{usermain}, …
  • Variable-based resolution, ~{${templateName} :: ${fragmentName}}
  • Fragment parameters, both named and unnamed, like ~{common::details(${user.name}), ~{::main(title=#{corp.title})}

Besides, Thymeleaf 3.0 fully embraces the new Markup Selector syntax provided by AttoParser 2.0, which builds on the previously existing syntax and largely extends it, providing new selection capabilities (see below).

Fragments as parameters (layout)

Now that fragments (objects of class org.thymeleaf.standard.expression.Fragment) can be put into context just as any other variables, we can use them as parameters to another fragments, effectively turning fragment expressions into a complete layout mechanism.

For example, we might develop a common template called base.html, containing the <head> tag we want to use for most of our application’s pages, containing all the style and script imports we will need but allowing the specification of a page title and some additional <link>s:

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

Then we could have a main.html template making use of the fragment above:

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

Note how those ~{::title} and ~{::links} parameters we are specifying in our th:replace will effectively pass parts of the original template markup (main.html) on to the inserted fragment (base.html), where these parts can be themselves inserted into specific placeholders. So the end result will look like:

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

Compatibility with fragment insertion in older versions

Even if the new fragment expressions bring a lot of flexibility to fragment insertion operations in Thymeleaf, this does not mean that old th:include or th:replace attributes need to be changed. So even if Thymeleaf 3.0 recommends using specifying fragment insertion like this:

<div th:replace="~{commons :: main}">...</div>

…this does not mean that the previous, unwrapped way of expressing fragments doesn’t work anymore. So this will still work perfectly:

<div th:replace="commons :: main">...</div>

Writing fragments as text, not as… fragments

A new possibility that fragment expressions bring is using fragment expressions outside fragment insertion attributes (th:insert/th:replace/th:include). Specifically, they can be useful in th:text and th:utext attributes:

<span th:utext="~{::title/text()}">...</span>

Note there are important differences between th:insert, th:text and th:utext when used with a fragment expression:

  • th:insert will process the inserted fragment (i.e. any th:* attributes in the fragment will get processed) while the others won’t.
  • th:text will escape the inserted fragment (after converting it to a String if needed) while the others won’t.
  • th:utext will therefore be almost equivalent to th:insert, but nothing inside the fragment will ever get processed.

So we can revisit the example above and rewrite the title of our base.html template, using a more elegant th:utext instead of the previous th:replace:

<head th:fragment="common_header(title,links)">

  <title th:utext="${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>

But note this will input the <title> tag coming from main.html inside the <title> tag in base.html, so we need to modify the expression by which we call the fragment from main.html in order to include only the title’s text body (/text()):

...
<head th:replace="base :: common_header(~{::title/text()},~{::link})">

  ...

</head>
...

Fragment existence

Fragment expressions do in general check the existence of the fragment they represent. But they do this in two different ways depending on when they are used:

  • For fragment expressions used directly (i.e. not as a part of a complex expression) in attributes such as th:insert/th:replace/th:include/th:text/th:utext/… these expressions will raise an exception if the resolved fragment does not exist. This includes both the fragment’s template not existing and also the specified fragment not existing in an existing template.
  • For fragment expressions used as a part of complex expressions (conditionals, defaults, etc.) or also as parameters of other fragment expressions, these expressions will simply evaluate to null whenever the fragment or its container template do no exist. Note that this nullity means that fragment expressions used as the condition of a conditional expression will evaluate to false.

Let’s see some examples. These will raise an exception if template or fragment do not exist:

<div th:insert="~{commons :: main}">...</div>
<div th:insert="commons :: main">...</div>
<div th:text="~{commons :: main}">...</div>

However, when used as a fragment expression parameter, it will simply evaluate to null:

<!-- Call the fragment with a "title" parameter that does not exist: title=null -->
<div th:insert="~{commons :: header(title=~{::#maintitle})}">...</div>
...
<!-- Once "title" is null th:utext's behaviour will be to write nothing -->
<span th:utext="${title}">...</span>
...
<!-- But note that, with the same null, th:insert's behaviour will be to fail -->
<span th:insert="${title}">...</span>

Also, when used in a conditional or default expression the fragment will evaluate to false (because null is false in conditionals). Let’s see a quite elaborate expression inserting a base_{TYPE} fragment depending on the type of user, but knowing that not all types actually exist in our template:

<div th:insert="~{commons :: |#base_${user.type}|} ?: ~{commons :: #default}">...</div>

The EMPTY fragment

If a nonexisting fragment will either fail or just return null, how can we make a fragment insertion expression insert nothing? In such case we can use the empty fragment expression: ~{}.

So we can modify the expression above to specify that, if no base for the user type exists, we should simply insert nothing:

<div th:insert="~{commons :: |#base_${user.type}|} ?: ~{}">...</div>

Note that inserting nothing is not the same as doing nothing. In this case, the ... in the body of the <div> tag will actually get removed and the result will be:

<div></div>

With this, we can revisit our header template and take into account the possibility that the calling template might not have any <link>s to add:

<head th:fragment="common_header(title,links)">

  <title th:utext="${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>

The new fragment syntax

Fragment expressions in Thymeleaf 3.0 allow the use of the powerful markup selector syntax provided by AttoParser 2.0, Thymeleaf’s new parsing library. This syntax builds on (and importantly extends) the fragment specification syntax available in Thymeleaf version 2.1, so that backwards compatibility is ensured.

For more detail on this syntax, read AttoParser’s documentation for this syntax at http://www.attoparser.org/apidocs/attoparser/2.0.0.BETA3/org/attoparser/select/package-summary.html

As a Thymeleaf-specific adaptation, note that what AttoParser generically calls reference-based matching is applied by Thymeleaf by considering the th:fragment and th:ref attributes as references. So the %whatever and whatever expressions will match any tags with th:fragment="whatever" or th:ref="whatever" (see #196 on th:ref).

Some very quick examples:

  • whatever or //whatever: match any <whatever> tags, or any whatever references (th:fragment="whatever", th:ref="whatever") at any level in the markup hierarchy.
  • /whatever: same as above, but only at the root level of the markup hierarchy.
  • foo/whatever: same as above, but only as a direct child of an element/reference called foo.
  • foo//whatever: same as above, but at any hierarchy level inside an element/reference called foo.
  • .whatever: any tags with class="whatever".
  • div.whatever: any <div> tags with class="whatever".
  • #whatever: any tags with id="whatever".
  • div#whatever: any <div> tags with id="whatever".
  • div#whatever/text(): any text nodes contained as direct children of a <div class="whatever">.
  • input[type='text']: anytags withtype=“text”`.

Issue Analytics

  • State:closed
  • Created 8 years ago
  • Reactions:10
  • Comments:17 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
silkentrancecommented, Mar 11, 2020

Will preprocessing still be possible, e.g.

layout:decorate="__${basetemplate}__"

I am asking because right now I get the deprecation warning (thymeleaf:3.0.12-SNAPSHOT)

Fragment expression "__${basetemplate}__" is being wrapped as a Thymeleaf 3 fragment expression (~{...}) for backwards compatibility purposes.  This wrapping will be dropped in the next major version of the expression processor, so please rewrite as a Thymeleaf 3 fragment expression to future-proof your code.  See https://github.com/thymeleaf/thymeleaf/issues/451 for more information.
1reaction
gomyhocommented, Sep 17, 2016

hi, I’m from china,i’m learning thymeleaf ,but when i use thyemleaf3.0 layout examples,it always throws exception,hope 4 solution. In my project(spring boot1.4,spring-boot-starter-thymeleaf) template files like this:

+templates
  +layout
    -header.html
  +strategy
    -create.html

all content in header.html (copy from http://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#template-layout)

<head th:fragment="common_header(title,links)">
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
    <title th:text="${title}">shangpin coupon manager system console</title>
        <th:block th:replace="${links}" />
</head>

and content in create.html

<!DOCTYPE html>
<html  xmlns:th="http://www.thymeleaf.org">
<head th:replace="layout/header :: common_header(~{::title} ,~{::link})">
    <title>coupon-create</title>
    <link rel="stylesheet" th:href="@{/public/css/strategy.css}" />
    <link rel="stylesheet" th:href="@{/public/css/strategy.css}" />
</head>

when i visit http://localhost/strategy/create,then it throws exception:

org.thymeleaf.exceptions.TemplateProcessingException: Could not parse as fragment selection: "layout/header :: common_header(~{::title} ,~{::link})" (strategy/create:3)
    at org.thymeleaf.standard.expression.FragmentSelectionUtils.parseFragmentSelection(FragmentSelectionUtils.java:106)
    at org.thymeleaf.standard.fragment.StandardFragmentProcessor.computeStandardFragmentSpec(StandardFragmentProcessor.java:72)
Read more comments on GitHub >

github_iconTop Results From Across the Web

Fragment of Power - Official Conan Exiles Wiki - Fandom
Relic Fragments in small chests hidden around the City and the Wine Cellar dungeon. They are primarily used to unlock the Library of...
Read more >
Phrases and Fragments - SlideShare
Fragment There are two major types of fragments: A. Phrase Fragments – consists of modified verbs without subject, prepositional, ...
Read more >
GLOSSARY OF MUSICAL TERMS
diatonic: a melody or harmony based on one of the seven-tone major or minor ... motive: a small musical fragment ("Lego" block) used...
Read more >
T Cells and MHC Proteins - Molecular Biology of the Cell - NCBI
Whereas B cells recognize intact antigen, T cells recognize fragments of protein ... MHC proteins are encoded by a large complex of genes...
Read more >
Fragment Based Drug Design - MiTeGen
Crystallography Sample Supports will be a next-generation crystal mounting system that changes how crystals... JBS:Frag Xtal Screen. A major challenge in drug ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found