Thymeleaf – Pagina templates voor zowel on- als offline gebruik

Styling is een onontkoombaar onderdeel van het web applicatie ontwikkelproces. Tijdens de start of gaandeweg een project komt dit altijd wel aan de orde. De layout voor een pagina aanmaken of aanpassen is in een werkende omgeving geen probleem. Indien er echter een layout gemaakt moet worden terwijl er geen web applicatie aanwezig is of buiten een werkende web applicatie (bijvoorbeeld door een designer) dan wordt het lastiger. De vraag die hierbij rijst is: hoe en in welk formaat moet het dan aangeleverd worden zodat het eenvoudig inpasbaar is?

Het Natural Templates concept

Ook al wordt de layout in statische HTML of in een ander formaat aangeleverd, er zal altijd een vertaalslag nodig zijn om deze te integreren met bestaande web pagina’s. Deze vertaalslag is te voorkomen middels het Natural Template concept: templates die ongewijzigd zowel binnen een web applicatie als ook statisch correct door een browser getoond kunnen worden.

Thymeleaf hanteert dit concept door middel van een template syntax die de document structuur intact houdt, waarbij de template een well-formed XML document blijft. Hiervoor wordt uitsluitend gebruik gemaakt van HTML attributen. Templates kunnen gebruikt worden als prototypen waarbij statische waarden getoond worden, en Thymeleaf zal deze run-time vervangen met live data.

Thymeleaf voorziet daarmee in de volgende belangrijke punten:

  • Geen conversie nodig van templates bij een layout update.
  • Eenvoudige integratie met een web-applicatie, doordat logica eenvoudig is toe te voegen zonder de layout te breken.

In dit artikel wordt aan de hand van code besproken hoe met Thymeleaf templates gemaakt kunnen worden, die zonder structurele aanpassingen zowel offline als binnen een web applicatie te gebruiken zijn. Daarnaast wordt er een voorbeeld gegeven hoe Thymleaf geïnstalleerd en geconfigureerd kan worden naast een ander web framework.

Onder de motorkap – een HTML 5 oriented template engine

Thymeleaf is een XML/XHTML(1.0 & 1.1)/HTML5 template engine en is uitbreidbaar voor andere formaten. Het project is gestart in 2011 en brengt bijna iedere maand een nieuwe versie uit. Thymeleaf gebruikt een XML parser en een DOM processor voor de interpretatie van de templates. Standaard wordt HTML5 goed ondersteund, zolang de geschreven template een well-formed XML bestand is.

Indien dit niet het geval is (bijvoorbeeld door integratie met legacy code), dan wordt de template eerst geconverteerd naar XML middels tag balancing. Hierbij worden fouten die tijdens het programmeren worden gemaakt gecorrigeerd, zoals het toevoegen van missende (parent) elementen en het verwijderen van ongeldige inline elementen.

De Thymeleaf DTD moet gedefinieerd zijn in de template zodat alle custom HTML attributes beschikbaar worden. Doordat deze attributen beschikbaar worden is de template een well-formed XML. Zodra een template is geïnterpreteerd door de template engine, worden al deze custom attributen verwijderd en de DTD declaratie vervangen door de XHTML 1.0 Strict definitie.

Van HTML naar Thymleaf templates

In dit artikel wordt een film catalogus demo gemaakt dat bestaat uit een lijst van films en de mogelijkheid om films toe te voegen en aan te passen. Momenteel is Spring MVC het enige web framework waarmee geïntegreerd kan worden. Uiteraard is het mogelijk om helemaal geen Spring te gebruiken. Het nadeel is dat koppeling tussen van de juiste controller en de bevraagde url dan geprogrammeerd moet worden. Vanwege de leesbaarheid van de code is daarom bij deze gekozen voor de SpringMVC integratie in combinatie met Spring. De demo applicatie is gebaseerd op Maven. Daarnaast is de Apache Tomcat als web server gebruikt.

We beginnen met een HTML bestand dat een lijst van films gaat tonen. Deze gaan we tijdens de voorbeelden omzetten naar Thymeleaf templates. Dit bestand bevat lijst dat aan de hand van statische gegevens wordt gevuld. De code voor de voorbeelden kan je met git clonen (git clone https://github.com/gmollema/ThymeleafDemo.git), en daarna bekijken door een specifieke branch uit te checken (git checkout -b example1)

Example 1 –  Statische data

Zodra je de inhoud van het list.html bestand (/WEB-INF/view/templates/pages/movie) bekijkt, zal je onder andere het volgende in de source zien:


<img src="../../../../images/logo_offline.png"/>
<h1>Home (offline)</h1>

 

Op dit moment is alleen de home pagina en de movie list pagina beschikbaar. Open ‘home.html’ met je browser en navigeer naar de movie list (zie figuur 1), de pagina die een lijst met films toont (sluit de pagina niet af, dit in verband met de volgende stap). Er wordt een lijst getoond van films op basis van statische data. Daarnaast is aan de titel en het logo de tekst offline toegevoegd (zie bovenstaande twee code regels), dat alleen getoond wordt als de pagina offline wordt geopend.


Figuur 1: Movie list (offline)

Example 2 – Statisch versus dynamisch

De volgende stap is de Thymleaf integratie om zo dynamische data te kunnen tonen. Om Thymeleaf de template te laten processen moeten een tweetal zaken doorgevoerd worden:

  • Thymeleaf configuratie opnemen in de Spring configuratie
  • Integratie met Thymleaf binnen de pagina zelf

Voor de configuratie zijn een drietal zaken geconfigureerd (terug te vinden in spring-thymeleaf-config.xml)

  • template resolver: definieert type templates die aanwezig zijn en waar deze te vinden zijn
  • template engine: het hart van Thymeleaf die de template resolver gebruikt om te weten naar welke templates geluisterd moet worden.
  • template view resolver: vertelt Thymeleaf uit welke context de templates gehaald moeten worden. In dit geval worden deze uit de servlet context gehaald.

De integratie in de pagina is vrij eenvoudig. In de head van de html moet de Thymeleaf namespace worden opgenomen. Vervolgens kan de namespace als html attribuut gebruikt worden op de html elementen om zo de dynamische data en functionaliteit toe te voegen. De twee regels code zijn nu als volgt geworden:


<img th:src="@{/images/logo.png}" src="../../../../images/logo_offline.png"/>
<h1 th:text="#{title.home}">Home (offline)</h1>

 

Thymleaf zal ervoor zorgen dat door middel van de th:src en th:text attributen de statische gegevens binnen de html tag worden overschreven.

Open je browser en voer een refresh uit. De pagina is ongewijzigd en laat dezelfde statische gegevens zoals voorheen zien.

Bouw de code met ‘mvn clean install’ Start tomcat, kopieer de war uit de target map naar de webapp map van een Tomcat installatie en open de volgende url in je browser: localhost:8080/ThymeleafDemo-1.0/home.html
De home pagina zal standaard geopend worden. Navigeer vervolgens naar de movie list (zie figuur 2).

Nu wordt de dynamische data getoond. Als je nu figuur 1 en 2 vergelijkt zie je dat Thymeleaf de titel, logo en de data heeft vervangen.


Figuur 2: Movie list

Thymeleaf syntax

Om de inhoud de syntax beter begrijpen volgt hier in het kort een beschrijving van de simpele expressies:

  • Variable Expressions: ${…} -Tonen van variabelen
  • Selection Variable Expressions: *{…} – Zelfde als $ echter op geselecteerd object
  • Message Expressions: #{…} – Externe resources voor teksten, context variabelen en utilities
  • Link URL Expressions: @{…}

Voorbeeld van eerste twee expressies:


<div th:object="${movie}">
  <p>Title: <span th:text="${movie.title}">Title</span>.</p>
  ...
</div>
<div>
  <p>Title: <span th:text="*{title}">Title</span>.</p>
   ...
</div>

 

De message expressions maken het onder andere mogelijk om meertalige teksten op te slaan. In de voorbeeldcode is een default gedefinieerd (message.properties) en Nederlands (messages_nl.properties). Zodra de browser op Nederlands ingesteld staat zal het deze vertalingen gebruiken en in alle andere gevallen de default.

Thymeleaf kent een aantal tegenhangers die de JSTL ook kent. Het Thymleaf th:each attribuut is bijvoorbeeld de tegenhanger van de JSTL tag c:forEach voor het itereren over een collectie van objecten.

Example 3 – Template fragmentatie

Thymeleaf ondersteunt template fragments. Hiermee kan je pagina onderdelen zoals header, footer, menu of pagina onderdelen die vaak gebruikt worden als een template fragment aanmaken. Vanuit de diverse pagina’s kan hier dan naar worden verwezen. In dit voorbeeld is de footer van de drie pagina’s apart getrokken naar een apart fragment. Deze is te vinden in de nieuwe fragments map binnen de folder templates.

Example 4 – Forms & validatie

In dit voorbeeld is een pagina opgenomen om een nieuwe film aan te maken of een bestaande film te bewerken. Onderaan de pagina is een link toegevoegd om een nieuwe film op te voeren en elke titel in de lijst is aanklikbaar om deze vervolgens te kunnen aanpassen. In beide gevallen wordt er een nieuw scherm getoond, waarin de gegevens kunnen worden ingevuld ofwel aangepast.

Zodra de gegevens worden opgeslagen worden er twee validatie slagen gedaan.

  • Voordat de save methode wordt aangeroepen van de MovieNewController, zal de initDateBinder methode het ingevoerd datum formaat controleren aan de hand van de syntax die gedefinieerd is in de message resources.
  • Vervolgens wordt in de save methode de validate methode van de NewMovieValidator aangeroepen om te controleren of de titel is ingevuld en er op z’n minst één genre is geselecteerd.

Hieronder volgt een screenshot van de validatie op het titel veld. De melding(en) worden getoond in het geel, en het veld waarom het gaat is voorzien van een rood kader (figuur 3).

Figuur 3: Add new movie

Middels de volgende code in de new.html pagina worden de errors getoond.


<ul th:if="${#fields.hasErrors('*')}" class="errorlist">
            <li> th:each="err : ${#fields.errors('*')}" th:text =="${err}" Input is incorrect</li>
</ul>

 

Aan elk invoerveld is aan de th:class attribuut een controle toegevoegd of the opgenomen is binnen de errors. Zo ja, dan wordt het veld gemarkeerd.

Example 5 : Servlet engine  in harmonie met de Thymeleaf engine

Om de servlet engine en Thymeleaf naast elkaar te kunnen laten draaien, is de mvc configuratie aangepast. In deze configuratie is een view resolver opgenomen die jsps opzoekt in de de /WEB-INF/view/jsp context. Alle Thymleaf html pagina’s blijven gezocht worden in de /WEB-INF/view/templates/pages context (zie de the Thymleaf configuratie).

Er is een nieuwe jsp pagina toegevoegd aan de jsp folder onder WEB-INF. Deze kan geopend worden door op de Movie counter link te klikken. Daaropvolgend zal de MovieCounterController worden aangeroepen die deze jsp gebruikt als view. Zodra op deze pagina op return Home geklikt wordt, wordt de HomeController aangeroepen en zal deze de home pagina zal laden, waarna neemt de Thymleaf view resolver het weer overneemt.

Nu is deze pagina geen rocket sience, maar dit is alleen om te laten zien dat beide engines naast elkaar kunnen draaien. De controllers maken gebruik van een hetzelfde sessie object om zo dezelfde lijst van films te kunnen gebruiken. Op deze manier is het mogelijk om Thymleaf templates te gebruiken, ook binnen een bestaand servlet gebaseerde web applicatie.

Bronnen

thymeleaf.org De Thymeleaf site bevat documentatie en duidelijke voorbeelden

github.com/gmollema/ ThymeleafDemo – De demo source code

Streamer: Thymeleaf zal ervoor zorgen dat door middel van de th:src en th:text attributen de statische gegevens binnen de html tag worden overschreven.>>