Wednesday, November 6, 2013

Is it worth upgrading to Thymeleaf 2.1?

Thymeleaf 2.1 release arrived. The new version brings plenty of new features in its core as well as in Spring Integration module like improvements of fragments inclusions, rendering view fragments directly from @Controller, improved form validation error reporting to name a few. But is it worth upgrading to Thymeleaf 2.1? Let's see.

Get started with 2.1

To get started with Thymeleaf 2.1 we need to upgrade our project. If you have no project and you want to start quickly, check the Summary section. I work with Maven so we will upgrade Maven dependencies in pom.xml:


        <properties>
            <org.thymeleaf-version>2.1.0.RELEASE</org.thymeleaf-version>
        </properties>

        <!-- View -->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf</artifactId>
            <version>${org.thymeleaf-version}</version>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring3</artifactId>
            <version>${org.thymeleaf-version}</version>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity3</artifactId>
            <version>2.1.0.RELEASE</version>
        </dependency>

Spring Security 3 module is using its own versioning, so I did not use the same variable, as you noticed. And remember to upgrade this module. Otherwise you may expect an runtime exceptions like the below:
org.thymeleaf.spring3.expression.SpelVariableExpressionEvaluator.computeContextVariables(Lorg/thymeleaf/Configuration;Lorg/thymeleaf/context/IProcessingContext;)Ljava/util/Map;

Improved form validation error reporting

For the following form:


<form class="form-narrow form-horizontal" method="post" th:action="@{/signup}" th:object="${signupForm}">
    <fieldset>
        <legend>Please Sign Up</legend>
        <div class="form-group" th:classappend="${#fields.hasErrors('email')}? 'has-error'">
            <label for="email" class="col-lg-2 control-label">Email</label>
            <div class="col-lg-10">
                <input type="text" class="form-control" id="email" placeholder="Email address" th:field="*{email}" />
                <span class="help-block" th:if="${#fields.hasErrors('email')}" th:errors="*{email}">Incorrect email</span>
            </div>
        </div>
        <div class="form-group" th:classappend="${#fields.hasErrors('password')}? 'has-error'">
            <label for="password" class="col-lg-2 control-label">Password</label>
            <div class="col-lg-10">
                <input type="password" class="form-control" id="password" placeholder="Password" th:field="*{password}"/>
                <span class="help-block" th:if="${#fields.hasErrors('password')}" th:errors="*{password}">Incorrect password</span>
            </div>
        </div>
        <div class="form-group">
            <div class="col-lg-offset-2 col-lg-10">
                <button type="submit" class="btn btn-default">Sign up</button>
            </div>
        </div>
        <div class="form-group">
            <div class="col-lg-offset-2 col-lg-10">
                <p>Already have an account? <a href="signin" th:href="@{/signin}">Sign In</a></p>
            </div>
        </div>
    </fieldset>
</form>

We are able to show a general error message outside the form element like this:

<div class="alert alert-dismissable alert-danger" th:if="${#fields.hasErrors('${signupForm.*}')}">
    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
    <span>Please correct the form and try again!</span>
</div>

<form class="form-narrow form-horizontal" method="post" th:action="@{/signup}" th:object="${signupForm}">
   ...
</form>

If you want to know more about form error handling see New th:errorclass for adding CSS class to form fields in error and Additional form validation error reporting options

Fragments inclusions improvements

As of Thymeleaf 2.1 we can pass parameters to template fragments which improves reusability and readability of the templates. With the combination of newly introduced th:block we can do the following:


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<body>
    <div th:fragment="alert (type, message)" class="alert alert-dismissable" th:classappend="'alert-' + ${type}">
        <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
        <span th:text="${message}">Test</span>
    </div>
</body>
</html>

This is standard template fragment that is placed in fragments/alert.html template. The fragment accepts two named parameters: type and messagea. These parameters are used to generate the content of the alert we want to include. To include the fragment we will do the following:

<th:block th:if="${#fields.hasErrors('${signupForm.*}')}">
    <div th:replace="fragments/alert :: alert (type='danger', message='Form contains errors. Please try again.')">Alert</div>
</th:block>

Please notice the usage of th:block that is an attribute container of whichever attributes. Thymeleaf will execute these attributes and then simply make the block disapear without a trace. So the above code works as follows: if there are errors in the form, include the alert fragment with type and message parameters. Pretty nice.

Having parametrized fragment we are able to reuse it in more templates, e.g. to display flash messages from the @Controller:


    <!-- /* Handling the flash message */-->
    <th:block th:if="${message != null}">
        <div th:replace="fragments/alert :: alert (type=${#strings.toLowerCase(message.type)}, message=${message.message})">&nbsp;</div>
    </th:block>

Summary

Thymeleaf 2.1 brings many improvements. In this post we saw couple of them only and I think it is worth upgrading to Thymeleaf 2.1. No doubt about it. If you want to learn more, visit What's new in Thymeleaf 2.1 page.

To get started quickly with Spring and Thymeleaf use Spring MVC Quickstart Archetype. You will find the above improvements in Thymeleaf branch.

EDIT. Inspired by one of the comments I wrote a post regarding template layouts in Thymeleaf. I hope you will find it interesting: Thymeleaf template layouts in Spring MVC application with no extensions

5 comments:

  1. To learn ThymeLeaf just visit http://thymeleaf-itutorial.appspot.com/.

    I've done the migration in one hour - mainly switching from div+th:with/div+th:substituteby to parameterized div+th:replace - now every "control" is just one line of HTML!

    And ThymeLeaf is our default view technology! It runs successfully in one of polish gov sites in application handling thousands of users and millions of form submissions!

    regards
    Grzegorz Grzybek

    ReplyDelete
  2. Hi Grzegorz,

    Thank you for your comment. Indeed, the tutorial is a great place to learn Thymeleaf.

    I am also happy to hear that you are using it on large scale. Do you use it a Tiles extension in this project? How many (approx) views you have in the project?

    It would be great to hear more details (the ones you can actually share of course).

    ReplyDelete
  3. Hello

    I actually don't use tiles/tiles-extensions - I've always thought that there's something wrong that controller returns tiles-definition name which is configured externally (tiles definition file) instead of returning what it should return - a view name which might be a "tile" put inside externally configured definition.

    I just do the following:

    1. Controllers, as always return view names (which translate to single Thymeleaf template file)
    2. I declare one global interceptor in RequestMappingHandlerMapping
    3. This interceptor changes the viewName in ModelAndView into a Thymeleaf view template file and the original viewName becomes "view" attribute in ModelAndView
    4. The layout file (there are two, three of them, e.g. "main", "security", "admin", ...) contains several th:substituteby's (in Thymeleaf 2.1: th:replace):
    - <div th:substituteby="${view} :: content">x</div>
    - <title th:substituteby="${view} :: title">
    - ...
    5. the actual view file (not layout file) contains fragments, "pulled" by the template which embeds the actual view:
    - <head><title th:fragment="title">xxx
    - <div th:fragment="content">...

    My application contains ~30 views - every one of them is "natural" template with working CSS and JS!

    regards
    Grzegorz Grzybek















    ReplyDelete
    Replies
    1. Grzegorz,

      You inspired me to write a blog post based on your comment. Check it out here: http://blog.codeleak.pl/2013/11/thymeleaf-template-layouts-in-spring.html

      I am looking forward to reading your comments!

      Delete