Using Thymeleaf in a DD4T project


read

I recently wrote a series of posts as I put together my latest development environment. I wanted to use it as an opportunity to talk about Thymeleaf; a server-side templating engine for Java, and why I’ve grown quite fond of it.

Note I’ll be installing Thymeleaf into my local SDL Web content delivery development environment, and talking about it in the context of SDL Web – but the same principles still apply universally to all projects leveraging Thymeleaf.

If you’ve never worked with SDL Web before – it’s an enterprise content management system.

Thymeleaf versus JSPs

It’s important to understand the difference; JSP is not a templating engine. JSPs are compiled to the servlet which then serves the web content, while Thymeleaf files are HTML files which are parsed and turned into web ready HTML. This offers plenty of advantages, but let me highlight a couple that have stood out so far to me:

  • The Thymeleaf expression language is much more capable than Springs
  • JSPs can’t be used outside of a container
  • Thymeleaf can be used outside of a container, therefore templates can be resolved in testing context etc
  • This makes unit testing templates a breeze
  • Easy to break down HTML into reusable chunks
  • Proves unbelievably helpful with DD4T strongly typed view models
  • Simplifies bundling and deployment of reusable utilities

Getting Setup

Getting setup with Thymeleaf is super easy. I’m going to set it up in my local environment that I mentioned earlier, so if you’re wondering what my starting point looks like – check out that series.

I’m going to review how to install Thymeleaf into my Java based DD4T-2 web application.

Add the following dependencies to pom.xml.

<dependency>
  <groupId>org.thymeleaf</groupId>
  <artifactId>thymeleaf</artifactId>
  <version>3.0.9.RELEASE</version>
</dependency>

<dependency>
  <groupId>org.thymeleaf</groupId>
  <artifactId>thymeleaf-spring3</artifactId>
  <version>3.0.9.RELEASE</version>
</dependency>

<dependency>
  <groupId>org.dd4t</groupId>
  <artifactId>dd4t-thymeleaf-support</artifactId>
  <version>2.0.7</version>
</dependency>

The required dependencies include:

  • the Thymeleaf library itself
  • the Spring Thymeleaf integration library
  • a DD4T Thymeleaf library which has several useful dialects for rendering Experience Manager markup, etc.

Update the dispatcher-servlet.xml file so that the viewResolver is using the ThymeleafViewResolver instead of the generic Spring MVC resource resolver.

<bean id="templateResolver" class="org.thymeleaf.spring3.templateresolver.SpringResourceTemplateResolver">
    <property name="prefix" value="/WEB-INF/views/" />
    <property name="suffix" value=".html" />
    <property name="templateMode" value="HTML" />
    <property name="cacheable" value="false" />
</bean>

<bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine">
    <property name="templateResolver" ref="templateResolver" />
    <property name="dialects">
        <set>
            <bean class="org.thymeleaf.spring4.dialect.SpringStandardDialect"/>
            <bean class="org.dd4t.thymeleaf.dialect.XpmDialect" />
        </set>
    </property>
</bean>

<bean id="viewResolver" class="org.thymeleaf.spring3.view.ThymeleafViewResolver">
    <property name="templateEngine" ref="templateEngine" />
    <property name="order" value="0" />
    <property name="excludedViewNames" value="*JSP" />
    <property name="characterEncoding" value="utf-8" />
</bean>

I already have a page template setup with DD4T TBBs called “Common Page Template”, which my index page is using which I set up in my previous blog series. I’m going to update the Common Page Template file from a JSP, to an HTML file with Thymeleaf syntax. I had been serving the JSP page until the installation of Thymeleaf which involved updating the view resolver.

My page now looks like:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Sample Page</title>
</head>

<body>
  <th:block th:utext="${pageModel.title}" />
</body>

</html>

Which should render out a blank page with the title of the page rendered in the body.

For the rest of the blog I’m going to cover some of my favorite features and how I’m leveraging them. Feel free to drop any questions below.

Reusable Blocks

Creating a reusable block in Thymeleaf is super easy, but also very flexible.

  • Reusable blocks are called “fragments” which can be defined with parameters (optional parameters too).
    • Embeddable schemas can have reusable fragments
    • Common lists of scripts, etc
  • Variable scope extends into fragment calls making it easy to create reusable blocks.
  • Template comments can be added which are automatically parsed out.
  • String, List & Map utilities exist which are very useful for templating.

To create a reusable piece of code, create a fragment in a new file:

  • views/components/common-scripts.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:xpm="http://www.sdl.com">

<!--/* Common Scripts Fragment */-->
<th:block th:fragment="common-scripts">
    <script src="https://code.jquery.com/jquery-2.1.1.js"  crossorigin="anonymous"></script>
</th:block>

</html>

And render the new fragment in common-page-template.html replacing the block with the fragment, named common-scripts, located in the components/scripts Thymeleaf file.

<th:block th:replace="components/scripts :: common-scripts"></th:block>

You can then take it a step further and pass in parameters. This is helpful for template variations, allowing enabling of certain template features, and more.

<th:block th:fragment="common-scripts(disableXpm)">

    <script src="https://code.jquery.com/jquery-2.1.1.js"  crossorigin="anonymous"></script>

    <!--/* If disableXpm is true, xpm.js will not be rendered in the DOM. */-->
    <th:block th:unless="${disableXpm}" >
        <script src="resources/js/xpm.js" type="text/javascript"></script>
    </th:block>

</th:block>

And actually – you don’t even need to define the parameter explicitly on the fragment definition if you want it to be an optional parameter.

Embeddable Schemas, View Models & Fragments

I think my favorite application of Thymeleaf fragments are with Embeddable Schemas. In my opinion, a solid SDL Web implementation will make good use of embeddable schemas for common field groups such as “Link” (internal link, external link, target behavior), “Image” (image, alt text, title) – etc.

DD4T produces strongly typed view models – including embeddable fields, which can be consumed and written out in Thymeleaf. So when we’re building out a new Thymeleaf template – we can make use of reusable fragments which know how to consume and render out sections of HTML based on standard input – which in this case is an embeddable schema used across lots of different schemas.

Embeddable Link Schema Example

Imagine the “Link” embeddable schema example I used which has several fields; an internal link field to allow component linking internally, an external URL text field to allow linking off the site and target behavior to control how the link opens in the browser. This schema will be rendered out in many variations of ‘call to actions’ – sometimes in multi-valued fields, sometimes not, but as long as there’s a fragment capable of consuming a single “Link” object, the code is reused.

Now imagine the following component is driving the component presentation shown above:

Component which renders out the above promotion with CTA.

We’re going to need to add some logic behind the call to action button rendered out depending on whether or not the user entered a component link (internal link), an external link etc. Now imagine the button style is reused across the site; it’s easy to imagine how you could end up with a bunch of duplicated code.

Instead – we add a ‘call to action’ fragment which consumes a Link object and renders out the button. We could even extend it by adding a parameter to the CTA fragment to allow passing in additional CSS classes so that the caller could change the default styling, etc.

When you go to build the next component presentation, which has a button with the same styling, you’ll be able to render out the same button:

Another template rendered against the sample component as above. Note that the CTA is consistent with the style on the other design, and should be reusable.

By calling the reusable fragment passing in the “Link” embeddable schema field on the view model.

<th:block th:replace="components/common :: call-to-action (link = ${viewModel.callToAction})"></th:block>

There’s a lot more I want to cover with Thymeleaf, but I’m going to cut it off here for now. Before I go, I wanted to cover a couple of the topics that I’d like to talk about in the future to try and spark some interest.

Custom Thymeleaf dialects

It’s easy to add Java code behind custom Thymeleaf attributes which can be used across your implementation. You can write your own dialect which has a backing processor to process the input data and update the DOM element.

The DD4T team wrote a custom dialect to consume page and component presentation objects and render out XPath for Experience Manager. When the Thymeleaf template is processed server-side, the backing code is called and the XPath comments are rendered inline.

Unit Testing thymeleaf classes

If you’re writing unit tests against your server-side code, it can be helpful to write unit tests against any of the Thymeleaf fragments which quite a bit of logic. It’s overwhelming to unit test all templates in my experience, especially without a backing front-end testing framework, but it’s nice to be able to write some unit tests against the most crucial Thymeleaf fragments in use (i.e. call to action fragment, image fragment which includes rendering of image SEO data etc).

Templates don’t need to be executed on the server

Thymleaf templates are simply HTML files with attributes recognized by Thymeleaf during processing. That means that HTML files can be handed back and forth between design and implementation teams and will render fine both on the server with real data, and off the server with hard-coded content (defined in the template and replaced when run on the server).

Final Thoughts

Thanks for reading and please leave any thoughts below.