Spring MVC part IV: ThymeLeaf advanced usage

This post follows on from the previous post on Thymeleaf integration with Spring MVC.

Today we’ll look at some advanced features of Thymeleaf

Please note that all the examples in this post can be found in a demo application on GitHub https://github.com/doanduyhai/ThymeLeafDemo

I Fragments inclusion

All the examples in this chapter can be found in the demo application on GitHub https://github.com/doanduyhai/ThymeLeafDemo, as example4.

A) General usage

A notheworthy templating engine has to provide a fragment system to avoid duplicating code. Fortunately Thymeleaf is shipped with a fragment inclusion system we’ll see shortly.

The main entry point for fragment inclusion is the th:include attribute. The syntax is: th:include=”fragment_file :: fragment_name|[DOM_selector].

The fragment_name or DOM_selector will be used to select the DOM object in the fragment_file file and inject it in the current document. The expression parsing and variable resolution will be performed after the fragment injection, not before. Please note that the DOM_selector needs to be put in square brackets. The DOM_selector uses a subset of XPath syntax, more details here.

Let’s define a template file common.html used as fragment container:

<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:th="http://www.thymeleaf.org">
<head id="headerFragment" th:fragment="headerFragment">
	<link rel="stylesheet" href="../assets/css/bootstrap.css" th:href="@{/assets/css/bootstrap.css}" />
	<link rel="stylesheet" href="../assets/css/thymeleaf-demo.css" th:href="@{/assets/css/thymeleaf-demo.css}" />
</head>
<body>
	<div class="container">
	</div>
</body>
</html>

Please notice the usage of th:fragment to define the fragment_name.

Now let’s write an example4.html page to test it:

<head th:include="fragment/common :: headerFragment" />
<body>
<div class="container">
	<br/>
	<br/>
	<br/>
	<div class="hero-unit">
	    <h1>ThymeLeaf is awesome !</h1>
	    <p>This is a simple hero-unit layout to demonstrate the fragment inclusion system</p>
	    <p>
	    	<a class="btn btn-primary btn-large" href="http://www.thymeleaf.org" target="_blank">
	    	Learn more about ThymeLeaf ...
	    	</a>
	    </p>
    </div>
</div>
</body>

headerFragment indicates the fragment_name defined in the common.html file previously.

If we wish to use DOM_Selector instead of fragment_name, the example4.html page should be modified as follow:

<head th:include="fragment/common :: [//head]" />
...

In this case there is no need to define the fragment_name with th:fragment in the common.html file.
 

B) Fragment container

With the powerfull selection mechanism for fragment, we can define several fragments in a same container file and include them in any of our page.

Let’s define a footer fragment in common.html

<footer id="center-footer">
	<div id="footerText" class="span12" >Copyright 2012 <a href="http://doanduyhai.wordpress.com">doanduyhai</a> |
       	 	<a href="https://github.com/doanduyhai/ThymeLeafDemo">Fork ThymeLeaf Demo on Github</a> |
        	<a href="https://twitter.com/#!/doanduyhai">Follow @doanduyhai on Twitter</a>
	</div>
</footer>

And then include it in our example4.html page:

...
<div th:include="fragment/common :: [//footer[@id='center-footer']]"></div>
...

Please note the quite verbose DOM selector expression to select the footer element. A simpler expression like “[//footer]” would be sufficient but I let it as is for the tutorial purpose.
 

C) Static preview and fragment inclusion

There is one problem with our fragment inclusion, it does not work at all for static page preview.

Below is the static display of the previous example:

thymeleaf-exemple4-static-fragment-preview

The rendering at runtime is:

thymeleaf-exemple4-runtime-fragment-render

The reason of this discrepancy is that in the static version the CSS styles are not available since there is no fragment inclusion.

To fix this, we simply add the stylesheet <rel> tags in the header of the static page. All this will be removed anyway at runtime by the fragment inclusion.

Edit: I’ve been informed by Daniel Fernández and Brian Keeter about Thymol,a javascript-base solution to perform fragment inclusion for static template preview. So there is no restriction anymore for static fragment inclusion.

 

II Thymeleaf and Spring EL (SpEL)

All the examples in this chapter can be found in the demo application on GitHub https://github.com/doanduyhai/ThymeLeafDemo, as example5.

When used in conjunction with Spring MVC, the default dialect used in the template files is Spring EL. It leverages Thymeleaf and offers a tremendous flexibility to manipulate data directly in the view.
 

A) Bean references & method invocation

Thymeleaf exposes a special object, beans, to let you access all Spring beans in the current application context.

Let’s define a dateFormatter bean as follow:

...
&lt;bean id=&quot;dateFormatter&quot; class=&quot;doan.thymeleaf.demo.util.DateFormatter&quot;/&gt;
...

The source code of the bean is quite straightforward:

public class DateFormatter
{
	private static final String DATE_FORMAT = &quot;yyyy-MM-dd HH:mm:ss&quot;;

	public String getCurrentDate()
	{
		return new DateTime(new Date()).toString(DATE_FORMAT);
	}
}

This bean defines a public getCurrentDate() method we’ll use in our page.

...
&lt;span th:text=&quot;${'Current date is : '+beans.dateFormatter.getCurrentDate()}&quot;&gt;Current date is : 2012-04-14 17:30:00&lt;/span&gt;
...

To access our bean named “dateFormatter“, we have to use the helper object “beans” provided by Thymeleaf. Under the hood beans is simply a Map which contains all references to the beans registered in the Spring context. There is really no magic here.
 

B) Static fields & methods

Spring EL provides the T( ) operator to access a class level(static) field or method.

...
&lt;span th:text=&quot;${'The application name is : '+T(doan.thymeleaf.demo.util.Constants).APPLICATION_NAME}&quot;&gt;The application name is : Test&lt;/span&gt;
...

This can be usefull to access some constants defined in the classpath without having to add the constant in the Model map.
 

C) Projection & selection on collection

Projection and selection are vocabulary from the relational world (SQL). Selection corresponds to row filtering (with WHERE clause) and projection corresponds to column restriction (the SELECT clause).

When applied to a collection of elements in Java:

  • selection means filtering each element of the collection using its properties. The result of such an operation is a subset of the initial collection, only matching elements are retained

.

  • projection means filtering the property of each element in the collection. The result of a projection operation is a a new collection, with the same number of elements than the original one, but containing only the filtered property of the elements, not the whole element object itself

The syntax of the selection operator is : collection.?[property == value]
The syntax of the projection operator is : collection.![property]

Of course, for selection we can combine several conditions with logical operators (and, or, not) to filter on different properties. Similarly, with the projection, it is possible to fetch several properties by concatenating them.

An example is better than words, let’s see those in action:

&lt;tr th:each=&quot;artist,rowStat : ${listArtits.?[alive == true]}&quot;&gt;
	&lt;td class=&quot;center middle&quot; th:text=&quot;${rowStat.count}&quot;&gt;1&lt;/td&gt;
	&lt;td class=&quot;center middle&quot; th:text=&quot;${artist.name}&quot;&gt;Michael Jackson&lt;/td&gt;
	&lt;td class=&quot;center middle&quot; th:text=&quot;${artist.discography}&quot;&gt;Got to Be There, Ben, Music &amp;amp; Me, Forever Michael...&lt;/td&gt;
	&lt;td class=&quot;center middle&quot; th:text=&quot;${artist.bio}&quot;&gt;Michael Joseph Jackson (August 29, 1958 - June 25, 2009) was an American recording artist, entertainer, and businessman...&lt;/td&gt;
&lt;/tr&gt;

Above we only select alive artists.

If we want to display only the firstname and lastname of the artist, we can “project” on both properties:

&lt;tr th:each=&quot;artist,rowStat : ${listArtits.![firstname+' '+lastname]}&quot;&gt;
	&lt;td class=&quot;center middle&quot; th:text=&quot;${rowStat.count}&quot;&gt;1&lt;/td&gt;
	&lt;td class=&quot;center middle&quot; th:text=&quot;${artist}&quot;&gt;Michael Jackson&lt;/td&gt;
&lt;/tr&gt;

Of course it is possible to combine selection and projection:

&lt;tr th:each=&quot;artist,rowStat : ${listArtits.?[alive == true].![firstname+' '+lastname]}&quot;&gt;
	&lt;td class=&quot;center middle&quot; th:text=&quot;${rowStat.count}&quot;&gt;1&lt;/td&gt;
	&lt;td class=&quot;center middle&quot; th:text=&quot;${artist}&quot;&gt;Michael Jackson&lt;/td&gt;
&lt;/tr&gt;

Please note that the order between projection and selection is important. If you “project” on a property before doing selection, the latter can only filter on this same property.

 

III Inlining & Javascript tricks

All the examples in this chapter can be found in the demo application on GitHub https://github.com/doanduyhai/ThymeLeafDemo, as example6.

A) Text inlining

Text inlining means that instead of being evaluated in the th:text attribute, the tag body is parsed by the engine and evaluated.

To do text inlining, we use the th:inline attribute with th:inline=”text”. The expression to be evaluated should be enclosed in double square brackets [[ ]]

...
&lt;span th:inline=&quot;text&quot;&gt;[[${'Current date is : '+beans.dateFormatter.getCurrentDate()}]]&lt;/span&gt;
...

The drawback of text inlining is the static page preview. What is displayed is the inlined expression itself:

thymeleaf-example6-text-inlining

B) Script inlining and variables propagation

With Thymeleaf it is also possible to evaluate SpEL expression inside a script tag. For the moment only Javascript and Google Dart are supported as script languages. To inline script, the value of th:inline should be set to “script”. The script code should also be enclosed between /*<![CDATA[*/ and /*]]>*/. Each SpEL expression inside the script body should be enclosed in /*[[ ]]*/

&lt;div&gt;
	&lt;button id=&quot;actionButton&quot; type=&quot;button&quot; onclick=&quot;displayApplicationName();&quot;&gt;Get Application name&lt;/button&gt;
	&lt;br/&gt;&lt;br/&gt;
	&lt;span id=&quot;displaySpan&quot;&gt;Application name : &lt;/span&gt;
&lt;/div&gt;
&lt;script th:inline=&quot;javascript&quot;&gt;
/*&lt;![CDATA[*/

	var applicationName = /*[[${T(doan.thymeleaf.demo.util.Constants).APPLICATION_NAME}]]*/ &quot;Test&quot;;

	function displayApplicationName()
	{
		document.getElementById('displaySpan').innerHTML='Application name : '+applicationName;
	}
/*]]&gt;*/
&lt;/script&gt;

One important thing to notice with script inlining is that it still works for static preview. Indeed the Javascript variable applicationName is declared with an inline expression followed by a default value: “Test“.

When displayed in a browser, the inlined expression between /*[[ ]]*/ will be ignored (considered as a comment) so the default value applies. A browser will see the variable declaration as:

var applicationName = “Test”;

At runtime, Thymeleaf will evaluate the inlined expression and everything that follows this expression will be stripped out. Thymeleaf will see the variable declaration as:

var applicationName = ${T(doan.thymeleaf.demo.util.Constants).APPLICATION_NAME}

Thus script inlining offers us a way to propagate variables defined in the server up to the Javascript layer. But what is it for ?

The idea behind the variables propagation is to define a variable value at only one place, on the server side. If they are defined at many places, changing them required code modification everywhere, with the risk of code discrepancy.
 

IV Helper objects awesomeness

All the examples in this chapter can be found in the demo application on GitHub https://github.com/doanduyhai/ThymeLeafDemo, as example2.

Thymeleaf was designed with productivity in mind. It comes with numerous helper objects to simplify developers’ life. Below is the complete list of available helper objects and their function:

  • dates : utility methods for java.util.Date objects: formatting, component extraction, etc.
  • calendars : analogous to #dates, but for java.util.Calendar objects.
  • numbers : utility methods for formatting numeric objects.
  • strings : utility methods for String objects: contains, startsWith, prepending/appending, etc.
  • objects : utility methods for objects in general.
  • bools : utility methods for boolean evaluation.
  • arrays : utility methods for arrays.
  • lists : utility methods for lists.
  • sets : utility methods for sets.
  • maps : utility methods for maps.
  • aggregates : utility methods for creating aggregates on arrays or collections.
  • messages : utility methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax.

It will take hours to go through all of them so we’ll only focus on the most usefull utility functions.

  • date formatting: ${#dates.format(date, ‘dd/MMM/yyyy HH:mm’)}
  • current time: ${#dates.createNow()}
  • empty or null string check: ${#strings.isEmpty(name)}
  • string operations: #strings.indexOf, #strings.substring, #strings.replace, #strings.prepend, #strings.append, #strings.toUpperCase, #strings.toLowerCase, #strings.trim, #strings.length, #strings.abbreviate
  • string collection manipulation: #strings.listJoin, #strings.arrayJoin, #strings.setJoin, strings.listSplit, strings.arraySplit, strings.setSplit
  • object null safe: ${#objects.nullSafe(obj,default)}
  • list manipulation: ${#lists.size(list)}, ${#lists.isEmpty(list)}, ${#lists.contains(list, element)}
  • message retrieval: ${#messages.msg(‘msgKey’)}, ${#messages.msg(‘msgKey’, param1, param2)}

Let’s put them in action in an example:

&lt;div class=&quot;alert&quot;
	th:if=&quot;${results != null and #lists.isEmpty(results) and searchAction != null}&quot;&gt;
    	&lt;strong&gt;No result found!&lt;/strong&gt; Please retry with different search filter(s).
&lt;/div&gt;
&lt;section id=&quot;searchResult&quot;&gt;
	&lt;table id=&quot;resultTable&quot;
		class=&quot;table table-bordered&quot;
		th:if=&quot;${results != null and not #lists.isEmpty(results)}&quot;&gt;
		&lt;thead&gt;
			&lt;tr class=&quot;center middle&quot;&gt;
				&lt;th&gt;#&lt;/th&gt;
				&lt;th&gt;Name&lt;/th&gt;
				&lt;th&gt;Discography&lt;/th&gt;
				&lt;th&gt;Bio&lt;/th&gt;
			&lt;/tr&gt;
		&lt;/thead&gt;
		&lt;tbody&gt;
			&lt;tr th:each=&quot;artist,rowStat : ${results}&quot;&gt;
				&lt;td class=&quot;center middle&quot; th:text=&quot;${rowStat.count}&quot;&gt;1&lt;/td&gt;
				&lt;td th:text=&quot;${artist.name}&quot;&gt;Mariah Carey&lt;/td&gt;
				&lt;td th:text=&quot;${#strings.abbreviate(#strings.listJoin(artist.discography,', '),30)}&quot;&gt;Mariah Carey, Emotions, Music Box, Merry Christmas, DayDream, Butter...&lt;/td&gt;
				&lt;td th:text=&quot;${#strings.abbreviate(artist.bio,100)}&quot;&gt;Mariah Carey (born March 27, 1970) is an American singer...&lt;/td&gt;
			&lt;/tr&gt;
		&lt;/tbody&gt;
	&lt;/table&gt;
&lt;/section&gt;

At lines 2 & 8 we use #lists.isEmpty() to check for result emptiness. Unfortunately this method is not null safe so a prior null check is required.

Edit: from ThymeLeaf version 2.0.7, null check is no longer required because isEmpty() methods in #arrays, #lists, #maps and #sets are null-safe.

At line 21 we use #strings.listJoin() to concatenate all the discography into a string and then apply #strings.abbreviate() to shorten it.

Below is the render at runtime:

V Spring Security integration

All the examples in this chapter can be found in the demo application on GitHub https://github.com/doanduyhai/ThymeLeafDemo, as example7.

Last but not least, we’ll see how Thymeleaf can be integrated with Spring Security. If you are an attentive reader if this blog, maybe you already read the post on Spring Security taglibs for JSP. If not I urge you to do so.

The problem is that this very convenient tag lib is only available for JSP, not for Thymeleaf. However with the flexibility of Spring MVC, it is possible to inject a security object in the Model map so it becomes available for the template engine at runtime.

The original solution was found by Zemi on the Thymeleaf forum, so the credits go to him. I only briefly expose the solution here.

First we need to create a custom interceptor for Spring MVC. This interceptor’ job is to inject the security object and make it available to the template engine:

public class SecurityInterceptor extends HandlerInterceptorAdapter
{
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception
	{
		if (modelAndView != null)
		{
			ServletRequest req = (ServletRequest) request;
			ServletResponse resp = (ServletResponse) response;
			FilterInvocation filterInvocation = new FilterInvocation(req, resp, new FilterChain()
			{
				public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException
				{
					throw new UnsupportedOperationException();
				}
			});

			Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
			if (authentication != null)
			{
				WebSecurityExpressionRoot sec = new WebSecurityExpressionRoot(authentication, filterInvocation);
				sec.setTrustResolver(new AuthenticationTrustResolverImpl());
				modelAndView.getModel().put(&quot;sec&quot;, sec);
			}
		}
	}
}

Basically we just inject into the Model map a WebSecurityExpressionRoot variable called “sec“.

Interceptor configuration for Spring MVC:

&lt;bean id=&quot;securityInterceptor&quot; class=&quot;doan.thymeleaf.demo.security.SecurityInterceptor&quot;/&gt;

&lt;bean class=&quot;org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping&quot;&gt;
	&lt;property name=&quot;interceptors&quot;&gt;
		&lt;list&gt;
			&lt;ref bean=&quot;securityInterceptor&quot; /&gt;
		&lt;/list&gt;
	&lt;/property&gt;
&lt;/bean&gt;

Now in the template file we can access the “sec” variable:

&lt;div class=&quot;span10 offset1&quot;&gt;
	&lt;span class=&quot;alert alert-success&quot; th:text=&quot;${'User login : '+sec.principal.username}&quot;&gt;User login : foo bar&lt;/span&gt;
	&lt;br/&gt;&lt;br/&gt;
	&lt;span class=&quot;alert alert-success&quot; th:text=&quot;${'User authorities : '+ #strings.listJoin(sec.principal.authorities,',')}&quot;&gt;User authorities : simple user&lt;/span&gt;
&lt;/div&gt;

That’s it folks!

8 Comments

  1. Daniel Fernández

    That “#lists.isEmpty” not being null-safe should be changed, it is not intuitive. I will modify it for next versions of Thymeleaf. Thanks for your very nice articles! 🙂

    Reply
  2. thymeleafer

    thank you sooooo much !!!

    Reply
  3. Fran Serrano

    Thanks for this very nice article for people that is already playing seriously with Thymeleaf. Promising future for this framework thanks to Daniel Fernández, the Thymeleaf team and bloggers like you ;O)

    Reply
  4. xtra size

    Appreciation to my father who told me on the topic of this weblog, this webpage
    is actually awesome.

    Reply
  5. Agnes

    For hottest news you have to pay a visit world wide web
    and on world-wide-web I found this web page as a best web page for newest updates.

    Reply
  6. peter

    thank you!
    chapter iii.B “enclose script” solved my problem where a style attribute inside a div tag inside a literal inside javascript code inside thymeleaf template broke either thymeleaf parsing at ‘ or the browser “SyntaxError: missing ; before statement”.

    original line was from a jqwidgets example on computed cell values:

    return “” + dataAdapter.formatNumber(total, “c2”) + “”;

    http://www.jqwidgets.com/jquery-widgets-demo/demos/jqxgrid/index.htm#demos/jqxgrid/computedcolumn.htm

    Reply
  7. Vip Domaine

    Thanks for the nice article. We are using this on one of our domains since 2015.
    I was wondering how to optimize this, n found the answer in the section “Helper objects awesomeness”
    Thanks again doanduyhai

    Reply
  8. Alex

    Great explanation!

    Reply

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.