- 1 Introducing Thymeleaf
- 2 The Good Thymes Virtual Grocery
- 3 Using Texts
- 4 Standard Expression Syntax
- 5 Setting Attribute Values
- 6 Iteration
- 7 Conditional Evaluation
- 8 Template Layout
- 9 Local Variables
- 10 Attribute Precedence
- 11 Comments and Blocks
- 12 Inlining
- 13 Textual template modes
- 14 Some more pages for our grocery
- 15 More on Configuration
- 16 Template Cache
- 17 Decoupled Template Logic
- 18 Appendix A: Expression Basic Objects
- 19 Appendix B: Expression Utility Objects
- 20 Appendix C: Markup Selector Syntax
4 Standard Expression Syntax
We will take a small break in the development of our grocery virtual store to learn about one of the most important parts of the Thymeleaf Standard Dialect: the Thymeleaf Standard Expression syntax.
We have already seen two types of valid attribute values expressed in this syntax: message and variable expressions:
<p th:utext="#{home.welcome}">Welcome to our grocery store!</p>
<p>Today is: <span th:text="${today}">13 february 2011</span></p>
But there are more types of expressions, and more interesting details to learn about the ones we already know. First, let’s see a quick summary of the Standard Expression features:
- Simple expressions:
- Variable Expressions:
${...}
- Selection Variable Expressions:
*{...}
- Message Expressions:
#{...}
- Link URL Expressions:
@{...}
- Fragment Expressions:
~{...}
- Variable Expressions:
- Literals
- Text literals:
'one text'
,'Another one!'
,… - Number literals:
0
,34
,3.0
,12.3
,… - Boolean literals:
true
,false
- Null literal:
null
- Literal tokens:
one
,sometext
,main
,…
- Text literals:
- Text operations:
- String concatenation:
+
- Literal substitutions:
|The name is ${name}|
- String concatenation:
- Arithmetic operations:
- Binary operators:
+
,-
,*
,/
,%
- Minus sign (unary operator):
-
- Binary operators:
- Boolean operations:
- Binary operators:
and
,or
- Boolean negation (unary operator):
!
,not
- Binary operators:
- Comparisons and equality:
- Comparators:
>
,<
,>=
,<=
(gt
,lt
,ge
,le
) - Equality operators:
==
,!=
(eq
,ne
)
- Comparators:
- Conditional operators:
- If-then:
(if) ? (then)
- If-then-else:
(if) ? (then) : (else)
- Default:
(value) ?: (defaultvalue)
- If-then:
- Special tokens:
- No-Operation:
_
- No-Operation:
All these features can be combined and nested:
'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))
4.1 Messages
As we already know, #{...}
message expressions allow us to link this:
<p th:utext="#{home.welcome}">Welcome to our grocery store!</p>
…to this:
home.welcome=¡Bienvenido a nuestra tienda de comestibles!
But there’s one aspect we still haven’t thought of: what happens if the message text is not completely static? What if, for example, our application knew who is the user visiting the site at any moment and we wanted to greet them by name?
<p>¡Bienvenido a nuestra tienda de comestibles, John Apricot!</p>
This means we would need to add a parameter to our message. Just like this:
home.welcome=¡Bienvenido a nuestra tienda de comestibles, {0}!
Parameters are specified according to the java.text.MessageFormat
standard syntax, which means you can format to numbers and dates as specified in the API docs for classes in the java.text.*
package.
In order to specify a value for our parameter, and given an HTTP session attribute called user
, we could have:
<p th:utext="#{home.welcome(${session.user.name})}">
Welcome to our grocery store, Sebastian Pepper!
</p>
Note that the use of
th:utext
here means that the formatted message will not be escaped. This example assumes thatuser.name
is already escaped.
Several parameters can be specified, separated by commas.
The message key itself can come from a variable:
<p th:utext="#{${welcomeMsgKey}(${session.user.name})}">
Welcome to our grocery store, Sebastian Pepper!
</p>
4.2 Variables
We already mentioned that ${...}
expressions are in fact OGNL (Object-Graph Navigation Language) expressions executed on the map of variables contained in the context.
For detailed info about OGNL syntax and features, you should read the OGNL Language Guide
In Spring MVC-enabled applications OGNL will be replaced with SpringEL, but its syntax is very similar to that of OGNL (actually, exactly the same for most common cases).
From OGNL’s syntax, we know that the expression in:
<p>Today is: <span th:text="${today}">13 february 2011</span>.</p>
…is in fact equivalent to this:
ctx.getVariable("today");
But OGNL allows us to create quite more powerful expressions, and that’s how this:
<p th:utext="#{home.welcome(${session.user.name})}">
Welcome to our grocery store, Sebastian Pepper!
</p>
…obtains the user name by executing:
((User) ctx.getVariable("session").get("user")).getName();
But getter method navigation is just one of OGNL’s features. Let’s see some more:
/*
* Access to properties using the point (.). Equivalent to calling property getters.
*/
${person.father.name}
/*
* Access to properties can also be made by using brackets ([]) and writing
* the name of the property as a variable or between single quotes.
*/
${person['father']['name']}
/*
* If the object is a map, both dot and bracket syntax will be equivalent to
* executing a call on its get(...) method.
*/
${countriesByCode.ES}
${personsByName['Stephen Zucchini'].age}
/*
* Indexed access to arrays or collections is also performed with brackets,
* writing the index without quotes.
*/
${personsArray[0].name}
/*
* Methods can be called, even with arguments.
*/
${person.createCompleteName()}
${person.createCompleteNameWithSeparator('-')}
Expression Basic Objects
When evaluating OGNL expressions on the context variables, some objects are made available to expressions for higher flexibility. These objects will be referenced (per OGNL standard) starting with the #
symbol:
#ctx
: the context object.#vars:
the context variables.#locale
: the context locale.
So we can do this:
Established locale country: <span th:text="${#locale.country}">US</span>.
You can read the full reference of these objects in Appendix A.
Expression Utility Objects
Besides these basic objects, Thymeleaf will offer us a set of utility objects that will help us perform common tasks in our expressions.
#execInfo
: information about the template being processed.#messages
: methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax.#uris
: methods for escaping parts of URLs/URIs#conversions
: methods for executing the configured conversion service (if any).#dates
: methods forjava.util.Date
objects: formatting, component extraction, etc.#calendars
: analogous to#dates
, but forjava.util.Calendar
objects.#temporals
: for dealing with dates and times using thejava.time
API in JDK8+.#numbers
: methods for formatting numeric objects.#strings
: methods forString
objects: contains, startsWith, prepending/appending, etc.#objects
: methods for objects in general.#bools
: methods for boolean evaluation.#arrays
: methods for arrays.#lists
: methods for lists.#sets
: methods for sets.#maps
: methods for maps.#aggregates
: methods for creating aggregates on arrays or collections.#ids
: methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).
You can check what functions are offered by each of these utility objects in the Appendix B.
Reformatting dates in our home page
Now we know about these utility objects, we could use them to change the way in which we show the date in our home page. Instead of doing this in our HomeController
:
SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
Calendar cal = Calendar.getInstance();
WebContext ctx = new WebContext(webExchange, webExchange.getLocale());
ctx.setVariable("today", dateFormat.format(cal.getTime()));
templateEngine.process("home", ctx, writer);
…we can do just this:
WebContext ctx = new WebContext(webExchange, webExchange.getLocale());
ctx.setVariable("today", Calendar.getInstance());
templateEngine.process("home", ctx, writer);
…and then perform date formatting in the view layer itself:
<p>
Today is: <span th:text="${#calendars.format(today,'dd MMMM yyyy')}">13 May 2011</span>
</p>
4.3 Expressions on selections (asterisk syntax)
Not only can variable expressions be written as ${...}
, but also as *{...}
.
There is an important difference though: the asterisk syntax evaluates expressions on selected objects rather than on the whole context. That is, as long as there is no selected object, the dollar and the asterisk syntaxes do exactly the same.
And what is a selected object? The result of an expression using the th:object
attribute. Let’s use one in our user profile (userprofile.html
) page:
<div th:object="${session.user}">
<p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>
Which is exactly equivalent to:
<div>
<p>Name: <span th:text="${session.user.firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="${session.user.nationality}">Saturn</span>.</p>
</div>
Of course, dollar and asterisk syntax can be mixed:
<div th:object="${session.user}">
<p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>
When an object selection is in place, the selected object will also be available to dollar expressions as the #object
expression variable:
<div th:object="${session.user}">
<p>Name: <span th:text="${#object.firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>
As said, if no object selection has been performed, dollar and asterisk syntaxes are equivalent.
<div>
<p>Name: <span th:text="*{session.user.name}">Sebastian</span>.</p>
<p>Surname: <span th:text="*{session.user.surname}">Pepper</span>.</p>
<p>Nationality: <span th:text="*{session.user.nationality}">Saturn</span>.</p>
</div>
4.4 Link URLs
Because of their importance, URLs are first-class citizens in web application templates, and the Thymeleaf Standard Dialect has a special syntax for them, the @
syntax: @{...}
There are different types of URLs:
- Absolute URLs:
http://www.thymeleaf.org
- Relative URLs, which can be:
- Page-relative:
user/login.html
- Context-relative:
/itemdetails?id=3
(context name in server will be added automatically) - Server-relative:
~/billing/processInvoice
(allows calling URLs in another context (= application) in the same server. - Protocol-relative URLs:
//code.jquery.com/jquery-2.0.3.min.js
- Page-relative:
The real processing of these expressions and their conversion to the URLs that will be output is done by implementations of the org.thymeleaf.linkbuilder.ILinkBuilder
interface that are registered into the ITemplateEngine
object being used.
By default, a single implementation of this interface is registered of the class org.thymeleaf.linkbuilder.StandardLinkBuilder
, which is enough for both offline (non-web) and also web scenarios based on the Servlet API. Other scenarios (like integration with non-ServletAPI web frameworks) might need specific implementations of the link builder interface.
Let’s use this new syntax. Meet the th:href
attribute:
<!-- Will produce 'http://localhost:8080/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html"
th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a>
<!-- Will produce '/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>
<!-- Will produce '/gtvg/order/3/details' (plus rewriting) -->
<a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>
Some things to note here:
th:href
is a modifier attribute: once processed, it will compute the link URL to be used and set that value to thehref
attribute of the<a>
tag.- We are allowed to use expressions for URL parameters (as you can see in
orderId=${o.id}
). The required URL-parameter-encoding operations will also be automatically performed. - If several parameters are needed, these will be separated by commas:
@{/order/process(execId=${execId},execType='FAST')}
- Variable templates are also allowed in URL paths:
@{/order/{orderId}/details(orderId=${orderId})}
- Relative URLs starting with
/
(eg:/order/details
) will be automatically prefixed by the application context name. - If cookies are not enabled or this is not yet known, a
";jsessionid=..."
suffix might be added to relative URLs so that the session is preserved. This is called URL Rewriting and Thymeleaf allows you to plug in your own rewriting filters by using theresponse.encodeURL(...)
mechanism from the Servlet API for every URL. - The
th:href
attribute allows us to (optionally) have a working statichref
attribute in our template, so that our template links remained navigable by a browser when opened directly for prototyping purposes.
As was the case with the message syntax (#{...}
), URL bases can also be the result of evaluating another expression:
<a th:href="@{${url}(orderId=${o.id})}">view</a>
<a th:href="@{'/details/'+${user.login}(orderId=${o.id})}">view</a>
A menu for our home page
Now that we know how to create link URLs, what about adding a small menu in our home page for some of the other pages in the site?
<p>Please select an option</p>
<ol>
<li><a href="product/list.html" th:href="@{/product/list}">Product List</a></li>
<li><a href="order/list.html" th:href="@{/order/list}">Order List</a></li>
<li><a href="subscribe.html" th:href="@{/subscribe}">Subscribe to our Newsletter</a></li>
<li><a href="userprofile.html" th:href="@{/userprofile}">See User Profile</a></li>
</ol>
Server root relative URLs
An additional syntax can be used to create server-root-relative (instead of context-root-relative) URLs in order to link to different contexts in the same server. These URLs will be specified like @{~/path/to/something}
4.5 Fragments
Fragment expressions are an easy way to represent fragments of markup and move them around templates. This allows us to replicate them, pass them to other templates as arguments, and so on.
The most common use is for fragment insertion using th:insert
or th:replace
(more on these in a later section):
<div th:insert="~{commons :: main}">...</div>
But they can be used anywhere, just as any other variable:
<div th:with="frag=~{footer :: #main/text()}">
<p th:insert="${frag}">
</div>
Later in this tutorial there is an entire section devoted to Template Layout, including deeper explanation of fragment expressions.
4.6 Literals
Text literals
Text literals are just character strings specified between single quotes. They can include any character, but you should escape any single quotes inside them using \'
.
<p>
Now you are looking at a <span th:text="'working web application'">template file</span>.
</p>
Number literals
Numeric literals are just that: numbers.
<p>The year is <span th:text="2013">1492</span>.</p>
<p>In two years, it will be <span th:text="2013 + 2">1494</span>.</p>
Boolean literals
The boolean literals are true
and false
. For example:
<div th:if="${user.isAdmin()} == false"> ...
In this example, the == false
is written outside the braces, and so it is Thymeleaf that takes care of it. If it were written inside the braces, it would be the responsibility of the OGNL/SpringEL engines:
<div th:if="${user.isAdmin() == false}"> ...
The null literal
The null
literal can be also used:
<div th:if="${variable.something} == null"> ...
Literal tokens
Numeric, boolean and null literals are in fact a particular case of literal tokens.
These tokens allow a little bit of simplification in Standard Expressions. They work exactly the same as text literals ('...'
), but they only allow letters (A-Z
and a-z
), numbers (0-9
), brackets ([
and ]
), dots (.
), hyphens (-
) and underscores (_
). So no whitespaces, no commas, etc.
The nice part? Tokens don’t need any quotes surrounding them. So we can do this:
<div th:class="content">...</div>
instead of:
<div th:class="'content'">...</div>
4.7 Appending texts
Texts, no matter whether they are literals or the result of evaluating variable or message expressions, can be easily appended using the +
operator:
<span th:text="'The name of the user is ' + ${user.name}">
4.8 Literal substitutions
Literal substitutions allow for an easy formatting of strings containing values from variables without the need to append literals with '...' + '...'
.
These substitutions must be surrounded by vertical bars (|
), like:
<span th:text="|Welcome to our application, ${user.name}!|">
Which is equivalent to:
<span th:text="'Welcome to our application, ' + ${user.name} + '!'">
Literal substitutions can be combined with other types of expressions:
<span th:text="${onevar} + ' ' + |${twovar}, ${threevar}|">
Only variable/message expressions (
${...}
,*{...}
,#{...}
) are allowed inside|...|
literal substitutions. No other literals ('...'
), boolean/numeric tokens, conditional expressions etc. are.
4.9 Arithmetic operations
Some arithmetic operations are also available: +
, -
, *
, /
and %
.
<div th:with="isEven=(${prodStat.count} % 2 == 0)">
Note that these operators can also be applied inside OGNL variable expressions themselves (and in that case will be executed by OGNL instead of the Thymeleaf Standard Expression engine):
<div th:with="isEven=${prodStat.count % 2 == 0}">
Note that textual aliases exist for some of these operators: div
(/
), mod
(%
).
4.10 Comparators and Equality
Values in expressions can be compared with the >
, <
, >=
and <=
symbols, and the ==
and !=
operators can be used to check for equality (or the lack of it). Note that XML establishes that the <
and >
symbols should not be used in attribute values, and so they should be substituted by <
and >
.
<div th:if="${prodStat.count} > 1">
<span th:text="'Execution mode is ' + ( (${execMode} == 'dev')? 'Development' : 'Production')">
A simpler alternative may be using textual aliases that exist for some of these operators: gt
(>
), lt
(<
), ge
(>=
), le
(<=
), not
(!
). Also eq
(==
), neq
/ne
(!=
).
4.11 Conditional expressions
Conditional expressions are meant to evaluate only one of two expressions depending on the result of evaluating a condition (which is itself another expression).
Let’s have a look at an example fragment (introducing another attribute modifier, th:class
):
<tr th:class="${row.even}? 'even' : 'odd'">
...
</tr>
All three parts of a conditional expression (condition
, then
and else
) are themselves expressions, which means that they can be variables (${...}
, *{...}
), messages (#{...}
), URLs (@{...}
) or literals ('...'
).
Conditional expressions can also be nested using parentheses:
<tr th:class="${row.even}? (${row.first}? 'first' : 'even') : 'odd'">
...
</tr>
Else expressions can also be omitted, in which case a null value is returned if the condition is false:
<tr th:class="${row.even}? 'alt'">
...
</tr>
4.12 Default expressions (Elvis operator)
A default expression is a special kind of conditional value without a then part. It is equivalent to the Elvis operator present in some languages like Groovy, lets you specify two expressions: the first one is used if it doesn’t evaluate to null, but if it does then the second one is used.
Let’s see it in action in our user profile page:
<div th:object="${session.user}">
...
<p>Age: <span th:text="*{age}?: '(no age specified)'">27</span>.</p>
</div>
As you can see, the operator is ?:
, and we use it here to specify a default value for a name (a literal value, in this case) only if the result of evaluating *{age}
is null. This is therefore equivalent to:
<p>Age: <span th:text="*{age != null}? *{age} : '(no age specified)'">27</span>.</p>
As with conditional values, they can contain nested expressions between parentheses:
<p>
Name:
<span th:text="*{firstName}?: (*{admin}? 'Admin' : #{default.username})">Sebastian</span>
</p>
4.13 The No-Operation token
The No-Operation token is represented by an underscore symbol (_
).
The idea behind this token is to specify that the desired result for an expression is to do nothing, i.e. do exactly as if the processable attribute (e.g. th:text
) was not there at all.
Among other possibilities, this allows developers to use prototyping text as default values. For example, instead of:
<span th:text="${user.name} ?: 'no user authenticated'">...</span>
…we can directly use ‘no user authenticated’ as a prototyping text, which results in code that is both more concise and versatile from a design standpoint:
<span th:text="${user.name} ?: _">no user authenticated</span>
4.14 Data Conversion / Formatting
Thymeleaf defines a double-brace syntax for variable (${...}
) and selection (*{...}
) expressions that allows us to apply data conversion by means of a configured conversion service.
It basically goes like this:
<td th:text="${{user.lastAccessDate}}">...</td>
Noticed the double brace there?: ${{...}}
. That instructs Thymeleaf to pass the result of the user.lastAccessDate
expression to the conversion service and asks it to perform a formatting operation (a conversion to String
) before writing the result.
Assuming that user.lastAccessDate
is of type java.util.Calendar
, if a conversion service (implementation of IStandardConversionService
) has been registered and contains a valid conversion for Calendar -> String
, it will be applied.
The default implementation of IStandardConversionService
(the StandardConversionService
class) simply executes .toString()
on any object converted to String
. For more information on how to register a custom conversion service implementation, have a look at the More on Configuration section.
The official thymeleaf-spring3 and thymeleaf-spring4 integration packages transparently integrate Thymeleaf’s conversion service mechanism with Spring’s own Conversion Service infrastructure, so that conversion services and formatters declared in the Spring configuration will be made automatically available to
${{...}}
and*{{...}}
expressions.
4.15 Preprocessing
In addition to all these features for expression processing, Thymeleaf has the feature of preprocessing expressions.
Preprocessing is an execution of the expressions done before the normal one that allows for modification of the expression that will eventually be executed.
Preprocessed expressions are exactly like normal ones, but appear surrounded by a double underscore symbol (like __${expression}__
).
Let’s imagine we have an i18n Messages_fr.properties
entry containing an OGNL expression calling a language-specific static method, like:
article.text=@myapp.translator.Translator@translateToFrench({0})
…and a Messages_es.properties equivalent
:
article.text=@myapp.translator.Translator@translateToSpanish({0})
We can create a fragment of markup that evaluates one expression or the other depending on the locale. For this, we will first select the expression (by preprocessing) and then let Thymeleaf execute it:
<p th:text="${__#{article.text('textVar')}__}">Some text here...</p>
Note that the preprocessing step for a French locale will be creating the following equivalent:
<p th:text="${@myapp.translator.Translator@translateToFrench(textVar)}">Some text here...</p>
The preprocessing String __
can be escaped in attributes using \_\_
.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论