Today we are going to look into Thymeleaf, a very innovative and full HTML5-oriented templating engine.
Thymeleaf, by definition, can be used as a standalone engine but when associated with Spring MVC, it gives the best of its essence.
Once you start using Thymeleaf, it’s very hard to go back to the good old JSP. JSP is good, JSP served us well during a decade but now we’re in 2012 and HTML 5 is the future so it’s time to look ahead.
Please note that all the examples in this post can be found in a demo application on GitHub https://github.com/doanduyhai/ThymeLeafDemo
I Introduction
Unlike many Java templating engines of its kind, Thymeleaf is a full HTML engine, meaning that the template file itself is a HTML file that can be rendered by any Web browser.
How can it be possible ? Well, Thymeleaf is an attribute-based template engine, in the sense that all the language syntax relies heavily on HTML tag attributes. For example (see example1 in the tutorial code):
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> ... ... <div class="container"> <br/> <br/> <br/> <div class="span4 offset4 row-fluid"> <div id="userMain" class="span1 table-bordered"> <a href="#"> <img id="picture" src="http://www.gravatar.com/avatar/" data-user="duyhai" th:src="${'http://www.gravatar.com/avatar/'+currentUser.gravatar+'?s=64'}" th:attr="data-user=${currentUser.login}"> </img> </a> </div> <div class="span3 table-bordered"> <h2> <span id="firstName" th:text="${currentUser.firstName}">DuyHai</span> <span id="lastName" th:text="${currentUser.lastName}">DOAN</span> </h2> </div> </div> </div> </html>
As we can see at lines 15,16,22 & 23, the template file is a mere HTML file with some special attribute prefixed by the Thymeleaf namespace th. A web browser will simply ignore these additional attributes whereas the template engine will process them.
As fas as I know, the only other attribute-based template engine out there is TAL (Template Attribute Language) using Python language.
The fact that the template itself can be displayed in a web browser is an important feature. Indeed while developing an Web page, people first start designing the static part of the GUI (css, color, layout) before focusing on the dynamic part (data injection, conditional rendering). With JSP you start creating an empty web page template before injecting all the tag soup with the risk of altering the initial static design. With Thymeleaf there is no such headache, we do not need to add any new tag, just new attributes.
Below is an example of a static page design (see example2 in the tutorial code):
As you can see, error and info message panels are all rendered at the same time and we also have some mocked search results.
At runtime, we get something similar to:
All mocked lines have been removed, only true search results are displayed (we’ll see later in this post how mocks are removed).
II How does it work
All the examples in this chapter can be found in the demo application on GitHub https://github.com/doanduyhai/ThymeLeafDemo, as example1.
It’s time to see how Thymeleaf work under the hood. If we go back to the example 1
Below is the static rendering:
Now let’s focus in the template code:
<img id="picture" src="http://www.gravatar.com/avatar/" data-user="duyhai" th:src="${'http://www.gravatar.com/avatar/'+currentUser.gravatar+'?s=64'}" th:attr="data-user=${currentUser.login}"> </img>
We can see there are 2 Thymeleaf attributes: th:src & th:attr. What happens at runtime is that Thymeleaf will parse your HTML document, pick any th attributes and perform substitution. Above, the src=”http://www.gravatar.com/avatar/” attribute will be replaced by an URL evaluated against the variable currentUser.gravatar (we’ll see soon where the variable comes from).
Similarly the <span id=”firstName” th:text=”${currentUser.firstName}”>DuyHai</span>
will be rendered as <span id=”firstName” >REAL_FIRSTNAME</span> at runtime, the existing DuyHai value is there only for the static preview of the template.
At runtime, we’ll get something similar to:
The general rule is that all static content & attributes will be replaced by dynamic one at runtime by the engine.
At that time, you attentive reader should say: “hey Dude, it means there should be as many Thymeleaf attributes as there are HTML 5 tag attributes!” and you’re almost right, almost, because Thymeleaf also exposes additional attributes for flow control.
Below is a list (not comprehensive) of some existing attributes:
- th:form
- th:alt
- th:action
- th:href
- th:src
- th:title
- th:value
- …
Please report to the official documentation for a full list of availables attributes.
Last but not least, Thymeleaf can use several dialects. A dialect is a set of rules and expressions used to parse and process a template file. Thymeleaf is shipped by default with the Standard dialect only. If you use Thymeleaf as view engine for Spring then you should add an extra package to use Spring EL (SpEL) as default dialect.
III Configuration in Spring MVC
To use Thymeleaf with Spring MVC, you should first add the following dependencies in the pom.xml file:
<dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf</artifactId> <version>2.0.5</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring3</artifactId> <version>2.0.5</version> <scope>compile</scope> </dependency>
The second dependency is necessary to use Spring EL as default dialect.
Below is the Spring XML configuration for Thymeleaf:
<bean id="templateResolver" class="org.thymeleaf.templateresolver.ServletContextTemplateResolver"> <property name="prefix" value="/" /> <property name="suffix" value=".html" /> <property name="templateMode" value="HTML5" /> <property name="cacheable" value="false"/> </bean> <bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine"> <property name="templateResolver" ref="templateResolver" /> </bean> <bean class="org.thymeleaf.spring3.view.ThymeleafViewResolver"> <property name="templateEngine" ref="templateEngine" /> <property name="characterEncoding" value="UTF-8"/> </bean>
And that’s all! Please note that for development cycle, I suggest to set the cacheable flag to false so you can see real time updates of your template modifications. Of course this flag should be set to true in production.
The variables used in the template files (such as currentUser in the first example) are passed to Thymeleaf via the Model object in Spring MVC @RequestMapping methods:
@RequestMapping(value = "/example1", method = RequestMethod.GET) public String example1(Model model) { model.addAttribute("currentUser", new User("foobar", "Foo", "BAR", "")); return "/pages/example1"; }
IV Commons attributes
All the examples in this chapter can be found in the demo application on GitHub https://github.com/doanduyhai/ThymeLeafDemo, as example3.
In this chapter we’ll look into most commons attributes used in Thymeleaf.
A) th:if/th:unless
These attributes are quite straightforward. They are the mirrors of <c:if> and <c:otherwise> from JSTL.
<div class="alert alert-error alert-block" th:if="${error != null}"> <h4 class="alert-heading">Error!</h4> An unexpected error occurs, please contact the support team </div> <div class="alert alert-success alert-block" th:unless="${error != null}"> <h4 class="alert-heading">Success!</h4> There is no error </div>
B) th:switch/th:case
<div th:switch="${switchCase}"> <p th:case="1">Case 1</p> <p th:case="2">Case 2</p> <p th:case="3">Case 3</p> <p th:case="4">Case 4</p> </div>
Not much to say, quite straightforward.
C) th:text
Thymeleaf replaces all the HTML content of the current tag by the value provided by th:text
... <span class="alert alert-success" th:text="${realText}">This is an unescaped sample text</span> ...
Please note that the text string provided by th:text will be escaped (HTML encoded) by Thymeleaf. If you want to include HTML tags in the text string, use the th:utext instead to avoid escaping.
D) th:attr
This attribute purpose is to add generic HTML attributes to a tag. This proves to be usefull when used with HTML 5 data-* attributes for data storage & binding.
<a href="#" data-url="http://www.google.com" title="Link to Google" th:attr="data-url=${dataUrl},title=${realTitle}" th:text="${dataUrl}"> http://www.google.com </a>
At line 4 we define 2 attributes to be evaluated by the engine: data-url & title. The Thymeleaf th:attr accepts a list of HTML attributes separated by a coma. Please note that for the title value we could have defined it with the dedicated th:title as well, the choice is yours.
The result after rendering is:
<a title=”Link to ThymeLeaf” href=”#” data-url=”http://www.thymeleaf.org”>
Usually we use the th:attr to add special HTML 5 data-* attributes. For common HTML attributes we use the existing Thymeleaf dedicated one.
E) th:value
The th:value attribute is reserved for HTML data input tags like <input>, <option>
<input class="input-medium" type="text" value="Name input" th:value="${nameInput}"> </input>
F) th:each
This is the most usefull tag when you need to iterate through a collection of data.
<table class="table table-bordered"> <thead> <tr class="center middle"> <th>Name</th> <th>Row count</th> <th>Row index</th> <th>List size</th> <th>Even count</th> <th>Odd count</th> </tr> </thead> <tbody> <tr th:each="artist,rowStat : ${listArtits}"> <td class="center middle" th:text="${artist.name}">John Woo</td> <td class="center middle" th:text="${rowStat.count}">1</td> <td class="center middle" th:text="${rowStat.index}">0</td> <td class="center middle" th:text="${rowStat.size}">1</td> <td class="center middle" th:text="${rowStat.even}">false</td> <td class="center middle" th:text="${rowStat.odd}">true</td> </tr> </tbody> </table>
At line 13, we define the th:each attribute. The syntax is: rowVariable[,rowStatVariable] : ${collection}
The rowStatVariable is an optional variable to keep track of the iteration status. This variable exposes the following properties:
- index: iteration index, starting at 0
- count: iteration count, starting at 1
- size: size of the list over which the iteration is done
- current: current value of the iteration
- even/odd: boolean to indicate whether the current element in the iteration is even or odd
- first/last: boolean to indicate whether the current element is the first/last element of the iteration
We realize that we could also use the current property of the rowStatVariable variable to access the current value. Instead of th:text=”${artist.name}” we could use th:text=”${rowStat.current.name}”
Please note that in this example, artist is a local variable used for the iteration. Its scope is limited to the <tr> tag. Any attempt to access artist outside of its scope will result in error.
G) th:with/th:object
th:with defines a local variable for the current context (e.g. current HTML tag and its children).
<div th:with="currentArtist=${listArtits[1]}"> <span class="label label-important" th:text="${currentArtist.name}">Michael Jackson</span><br/> <span class="label label-info" th:text="${currentArtist.bio}">Michael Joseph Jackson (August 29, 1958 - June 25, 2009) was an American recording artist, entertainer, and businessman...</span><br/> <span class="label label-inverse" th:text="${currentArtist.discography}">Got to Be There, B...</span> </div>
On the other hand th:object defines a selected object that can be accessed in the current context (e.g. current HTML tag and its children) by the special asterisk *{ } short-hand syntax.
The meaning of this attribute is similar to the With() syntax in C# language.
<div th:object="${listArtits[0]}"> <span class="label label-important" th:text="*{name}">Michael Jackson</span><br/> <span class="label label-info" th:text="*{bio}">Michael Joseph Jackson (August 29, 1958 - June 25, 2009) was an American recording artist, entertainer, and businessman...</span><br/> <span class="label label-inverse" th:text="*{discography}">Got to Be There, B...</span> </div>
The main difference between th:object and th:with is that th:object defines a selected object in the current context accessible by the special *{ } syntax. All property look-up inside a *{ } expression is done with regard to the selected object. Inside the same context the standard syntax with ${ } can still be used though.
On the other hand th:with will create a new temporary variable in the variable map (Spring Model object) whose scope is limited to the current context.
H) th:remove
This attribute is usefull for template static preview. Let’s suppose that we have a template file with a table containing a list of artists. We will design the table using the th:each attribute as described previously to iterate through the result list.
However our page will look very poor in a static display since it only has one row. We could remedy to this by adding extra mocked rows but they need to be removed at runtime. That’s the purpose of th:remove
<tbody> <tr th:each="artist,rowStat : ${listArtits}"> <td class="center middle" th:text="${rowStat.count}">1</td> <td class="center middle" th:text="${artist.name}">Michael Jackson</td> <td class="center middle" th:text="${artist.discography}">Got to Be There, Ben, Music &amp; Me, Forever Michael...</td> <td class="center middle" th:text="${artist.bio}">Michael Joseph Jackson (August 29, 1958 - June 25, 2009) was an American recording artist, entertainer, and businessman...</td> </tr> <tr th:remove="all"> <td class="center middle" th:text="${rowStat.count}">2</td> <td class="center middle" th:text="${artist.name}">Madonna</td> <td class="center middle" th:text="${artist.discography}">Madonna, Like a Virgin, True Blue...</td> <td class="center middle" th:text="${artist.bio}">Madonna (born Madonna Louise Ciccone August 16, 1958) is an American singer, songwriter, actress and entrepreneur...</td> </tr> <tr th:remove="all"> <td class="center middle" th:text="${rowStat.count}">3</td> <td class="center middle" th:text="${artist.name}">Elton John</td> <td class="center middle" th:text="${artist.discography}">Empty Sky, Elton John, Tumbleweed Connection...</td> <td class="center middle" th:text="${artist.bio}">Sir Elton Hercules John, CBE (born Reginald Kenneth Dwight on 25 March 1947) is an English rock singer-songwriter, composer, pianist and occasional actor....</td> </tr> </tbody>
The second and third row (line 8 & 14) will be removed at runtime by the engine. However if we have many mocked rows we should add the th:remove for each of them which is quite tedious.
The solution is given again by th:remove with the value “all-but-first” instead of “all“. It tells the engine to remove all the children of the current tag except the first one. The above code can be modified to take advantage of this feature.
<tbody th:remove="all-but-first"> <tr th:each="artist,rowStat : ${listArtits}"> <td class="center middle" th:text="${rowStat.count}">1</td> <td class="center middle" th:text="${artist.name}">Michael Jackson</td> <td class="center middle" th:text="${artist.discography}">Got to Be There, Ben, Music &amp; Me, Forever Michael...</td> <td class="center middle" th:text="${artist.bio}">Michael Joseph Jackson (August 29, 1958 - June 25, 2009) was an American recording artist, entertainer, and businessman...</td> </tr> <tr> <td class="center middle" th:text="${rowStat.count}">2</td> <td class="center middle" th:text="${artist.name}">Madonna</td> <td class="center middle" th:text="${artist.discography}">Madonna, Like a Virgin, True Blue...</td> <td class="center middle" th:text="${artist.bio}">Madonna (born Madonna Louise Ciccone August 16, 1958) is an American singer, songwriter, actress and entrepreneur...</td> </tr> <tr> <td class="center middle" th:text="${rowStat.count}">3</td> <td class="center middle" th:text="${artist.name}">Elton John</td> <td class="center middle" th:text="${artist.discography}">Empty Sky, Elton John, Tumbleweed Connection...</td> <td class="center middle" th:text="${artist.bio}">Sir Elton Hercules John, CBE (born Reginald Kenneth Dwight on 25 March 1947) is an English rock singer-songwriter, composer, pianist and occasional actor....</td> </tr> </tbody>
The th:remove supports some more values, please refer to the documentation for the complete list.
To be continued…
Pingback: Spring MVC part VI: ThymeLeaf advanced usage « Yet Another Java Blog
I ve recently changed my mind from using Play on GAE to using Spring 3 ( + extensions) integrated with Thymeleaf (still on GAE)…
… Your post was very helpful i ve saved time ๐
hi doan ๐
i cant run the examples that on githup.please help me
hello hadi
what issues did you have ?
Pingback: Confluence: Union Bank of California
I found this helpful, having worked with Thymeleaf a little, it’s nice to see the work of some one who knows his way around.
I really appreciated the example app, it worked perfectly for me. I learned a few things about the differences between th:with and th:object. Also the section on th:remove was very helpful to me.
Thanks
Love the post! Thanks for this. Thymeleaf needs to have some more documentation on the different utility methods it supports (Without reading the source code). Your posts have helped me in finding this info.
You’re welcomed
Hello,
I am very new to java and i have to work with spring3+thymeleaf.
Though your posts are useful but i am not able to completely understand .
Can you please step by step give some tutorial as to how to develop a simple web application in eclipse using spring3 framework with thymeleaf.?
Also what all jar files are needed for the same..!
Hello Madhurika
You can check my Github project : https://github.com/doanduyhai/ThymeLeafDemo
All the source code to build a working Web Application with Thymeleaf is there
thanks.:)
Hello..
I have to take one input and accordingly fetch my data from database and then have to display in the form of table in the same page using thymeleaf+spring3.
I have tried alot but failed miserably..can you help.?
This blog, Solar Shades โSpring MVC part III: ThymeLeaf integration | DuyHai’s Java Blogโ was amazing. Iโm impressing out a replicate to show my associates. Thanks a lot-Amparo
Happy that it helps ๐
Thanks for the good introduction into thymeleaf, helping a lot to get started!
BTW, wicket templates are also pure HTML, regarding your comment on TAL being the only other one you know.
Pingback: My second step with Thymeleaf (migrating JSPX generated by Spring Roo) | Trying things
Concise tutorial on Thymeleaf…thanks!
hi doanduyhai…
i got your code from github, It’s Really Helpfull to me..
Hi Hai,
I happened to discover your discussion here.
I am interested in Web Development myself and would like to have contact with you to exchange ideas.
Regards
Lan
Good one! Thanks ๐
dialect for using data attributes: https://github.com/mxab/thymeleaf-extras-data-attribute
I’ve learn a few good stuff here. Certainly
worth bookmarking for revisiting. I wonder how a lot effort
you set to make any such fantastic informative site.