Archives Wild Code School
Spring

Spring 03 : Thymeleaf

Quête 3 sur 16

Afficher de l'HTML et du CSS statique a ses limites : si tu souhaitais créer un webmail avec Spring Boot :

  • comment afficher le nom de l'utilisateur connecté ?
  • comment afficher la liste de ses emails ?
  • comment afficher le contenu d'un email spécifique ?

En effet, toutes ces données sont dynamiques. Tu vas découvrir avec cette quête le moteur de template Thymeleaf, qui va te permettre de gérer tous ces cas !

Objectifs

  • Générer du contenu dynamique avec Thymeleaf
  • Afficher une liste d'éléments

Moteur de template

Avec Jakarta EE, il est possible d'ajouter du code Java combiné à de l'HTML en utilisant des scriptlet :

java
<p><% out.print("Hello World"); %></p>

De plus, il existe la bibliothèque JSTL (The JSP Standard Template Library) qui apporte des fonctionnalités supplémentaires comme les boucles, par exemple :

java
<c:forEach begin="1" end="4" var="i">
<p><c:out value="${i}"/></p>
</c:forEach>

Cependant, dans ces deux cas, il te faut utiliser le format jsp plutôt que html.

Un fichier jsp (Java Server Pages) doit être compilé et exécuté sur un serveur web afin d'afficher correctement les scriptlets et les balises JSTL qu'il contient : l'affichage ne sera pas correct si le fichier est juste glissé-déposé directement dans un navigateur.

Thymeleaf est un moteur de template : c'est un outil qui permet d'insérer du code dans une page html.

Il autorise donc de travailler dans un fichier html classique, en ajoutant des propriétés aux balises déjà présentes. Comme pour les jsp, le contenu dynamique n'apparaîtra que lors de l'exécution du serveur web. Cependant, il est possible, sans compilation, d'afficher une prévisualisation correcte de la page html dans un navigateur : pratique pour réaliser le style CSS !

Thymeleaf est donc appréciable dans une approche MVC, qui consiste à séparer la vue du contrôleur.

↗
A la découverte de Thymeleaf !!

Un article du blog // happy coding présentant Thymeleaf en le comparant à d'autres solutions

patouche.github.io

Contenu dynamique et statique

Pour initialiser un projet Spring Boot avec Thymeleaf, ouvre Spring Initializr et ajoute les dépendances Spring Web et Thymeleaf.

Le principe de Thymeleaf est d'afficher du contenu dynamique en Java, en utilisant des propriétés HTML. Imaginons que tu veuilles créer une page web qui affiche le nom de l'utilisateur connecté : selon l'utilisateur, le contenu sera différent.

À partir du moment où tu utiliseras Thymeleaf, il ne faudra plus déposer de fichiers html dans le dossier src/main/resources/static, pour une raison simple : il ne s'agit plus de simples fichiers statiques mais de fichiers dynamiques, appelés templates !

Les images, feuilles de styles css et script JavaScript devront toujours être stockés dans static par contre.

Commençons par créer un template nommé home.html, dans le dossier src/main/resources/templates. Il contient le code nécessaire à l'affichage de "Hello World!" :

html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title>Thymeleaf</title>
  <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
</head>
<body>
  <h1>Hello World!</h1>
</body>
</html>

Tu remarqueras l'utilisation de l'expression xmlns:th="http://www.thymeleaf.org" dans la balise html : elle est nécessaire au bon fonctionnement de Thymeleaf.

Pour afficher la page, tu vas créer une route dans un contrôleur :

java
@GetMapping("/")
public String index() {

  return "home"; // you can also write return "home.html"
}

L'url http://localhost:8080 affichera le contenu de la page home.html.

Il ne faut pas mettre l'annotation @ResponseBody, car elle afficherait une chaîne de caractère et non un template.

↗
Différence entre les pages Web statiques et dynamiques

Si tu veux des définitions plus précises sur la différence entre des contenus statique et dynamiques

waytolearnx.com

Transmettre la valeur d'une variable

En l'état actuel, ta page, bien que dynamique, affiche un contenu statique : en effet, le texte sera toujours le même : "Hello World!".

Imagines que tu veuilles modifier le système pour afficher "Hello $name!", ou $name serait remplacé par la valeur d'une variable envoyée depuis le contrôleur (pour simuler, par exemple, l'appel d'une base de données).

La première étape va consister à créer une route qui initialise une variable contenant le nom de la personne.

Dans un contrôleur, tu vas ajouter la variable correspondante :

java
@GetMapping("/")
public String index() {

  String name = "Bernard";

  return "home";
}

Pour envoyer la valeur d'une variable à une page web, Spring MVC t'offre un objet Model : tout attribut ajouté à cet objet sera transmis à la page HTML.

L'objet Model doit être ajouté en paramètre de la méthode et la transmission de données passe par l'utilisation de addAttribute(String key, Object value) :

java
@GetMapping("/")
public String index(Model model) {

  String name = "Bernard";

  model.addAttribute("name", name);

  return "home";
}

Du côté du template home.html, il va te falloir récupérer la valeur de la variable afin de l'afficher. Il s'agira dans ce cas là d'ajouter l'expression th:text à la balise h1, avec la variable au format ${variable} :

html
<h1 th:text="${name}">Hello World!</h1>

À l'affichage de la page, le texte "Hello World!" sera remplacé par la valeur de la variable name, soit, dans notre exemple : "Bernard".

Imaginons maintenant que tu veuilles mettre en forme la variable dans la phrase "Hello Bernard!".

Pour cela il te faudra entourer ta chaîne par le caractère | (pipe) :

html
<h1 th:text="|Hello ${name}!|">Hello World!</h1>

↗
Spring Boot Hello World Example with Thymeleaf

En suivant ce tuto, tu pourras mettre en pratique l'exemple vu précédemment

hellokoding.com

Les expressions

Thymeleaf possède des expressions qui permettent d'afficher tout un tas de choses utiles. Nous avons déjà vu l'expression th:text et l'expression ${variable} afin d'afficher le contenu d'une variable, mais ce n'est pas tout !

1. Boucle

Tu pourras par exemple parcourir une liste d'objets afin d'afficher les valeurs de ses attributs, en utilisant l'expression th:each et sa valeur sous forme item : ${list} :

html
<ul th:each="food : ${lunch}">
  <li th:text="${food.name}">Name</li>
  <li th:text="${food.calories}">Calories</li>
</ul>

On remarque que l'accès aux attributs se fait avec la forme ${item.attribute}. Cependant, il faut que la classe de l'objet ait bien des getters (accesseurs) associés aux attributs pour que cela fonctionne.

Ici, pour chaque plat du menu, une liste présentant le nom du plat et son nombre de calories sera affichée. Voici un exemple de code généré à l'affichage :

html
<ul>
  <li>Big Wac</li>
  <li>450</li>
</ul>
<ul>
  <li>French fries</li>
  <li>275</li>
</ul>
<ul>
  <li>Chocolate Sunday</li>
  <li>175</li>
</ul>

Tu noteras que c'est bien l'élément avec le th:each qui a été répété. Si tu avais voulu créer une seule liste avec plusieurs éléments, tu aurais plutôt utilisé ce code :

html
<ul>
  <li th:each="food : ${lunch}" th:text="${food.name}">Name</li>
</ul>

Ce qui aurait généré à l'affichage :

html
<ul>
  <li>Big Wac</li>
  <li>French fries</li>
  <li>Chocolate Sunday</li>
</ul>

2. Sélection

Une autre expression, dite "de sélection", te permet d'afficher les attributs d'un objet parent défini plus tôt. Il te faudra utiliser l'expression th:object sur un conteneur, avec la référence de l'objet sous la forme ${variable}.

Ensuite, pour les éléments enfants, tu pourras appeler les attributs de l'objet avec l'expression *{attribute}.

html
<div th:object="${food}">
  <span th:text="*{name}">Name</span>
  <span th:text="*{calories}">Calories</span>
</div>

3. Lien d'URL

Afin d'éviter de mettre des liens "en dur" (contenant le chemin complet) et ainsi être sûr que le chemin est correct quelle que soit la session de l'utilisateur et ses paramètres d'url, il te sera conseillé d'utiliser l'expression de lien th:href avec la valeur sous la forme @{/your/route}.

html
<a th:href="@{/home}">Home page</a>

Il est aussi possible de passer des paramètres de la façon suivante :

html
<a th:href="@{/home(name=${name},age=${age})}">Home page</a>

Ne te contente pas de connaître uniquement ce qui est présenté ci-dessus : le lien en ressource va t'expliquer d'autres expressions importantes !

↗
Getting started with the Standard dialects in 5 minutes

Pour la partie concernant les fragments, tu verras cela en détail dans la section suivante

thymeleaf.org

Fragments

Un site Internet réutilise beaucoup de code dans ses pages, par exemple un entête et un pied de page qui seront sensiblement identiques sur tout le site.

Il serait fastidieux de devoir copier-coller leurs contenus sur chaque page, sans compter que c'est une source d'erreur : en cas de modification, il faudrait l'impacter sur toutes les pages !

1. Création d'un fragment

Pour résoudre ce problème, Thymeleaf donne accès à la notion de fragments : un template html est réalisé pour un morceau de page, par exemple header.html. Son code ne va contenir que ce qui est relatif à son rôle : afficher l'en-tête.

Tu vas donc créer un fichier html complet : contenant le doctype, le head et ses éventuels styles, ainsi qu'un body. L'avantage de créer un fichier HTML complet est qu'en l'ouvrant dans un navigateur, sans avoir à lancer le serveur, l'en-tête s'affichera parfaitement.

Ensuite, tu devras indiquer quelle partie du fichier doit être réutilisée dans d'autres pages, grâce à l'expression th:fragment et à une valeur choisie pour le nom du bloc réutilisable (ici top) :

html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
</head>
<body>
  <header th:fragment="top">
    <a th:href="@{/}">Home page</a>
  </header>
</body>
</html>

Si tu souhaites insérer l'en-tête dans une autre page, par exemple home.html, tu vas pouvoir utiliser l'expression to:replace en appelant le fichier et le nom du bloc réutilisable au format /path/to/fragment.html :: bloc :

html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title>Thymeleaf</title>
  <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
</head>
<body>
  <header th:replace="header.html :: top">
    Your header here
  </header>
</body>
</html>

Note : le .html du fragment est optionnel, tu peux simplifier en th:replace="header :: top"

Tu trouveras d'autres expressions à utiliser avec les fragments en ressource, notamment th:insert. Tu verras qu'il est aussi possible d'afficher des fragments sous certaines conditions.

↗
Thymeleaf Page Layouts
thymeleaf.org

Challenge

Serveur d'identification des docteurs

Le docteur veut toujours créer son site web. Mais comme il est toujours pressé, il n'a pas lu le manuel de Thymeleaf : aide-le à corriger son code.

Pour démarrer, fais un Fork du dépôt suivant puis clone-le en local.

Pense bien à faire un Fork, sinon tu ne pourras rien pousser !

Critères de validation

  • Ton projet affiche bien le nom et le numéro du docteur correspondant aux liens

  • Si aucun nom ni numéro n'est renseigné, l'affichage doit être "John Smith" et 0

  • Les templates index.html et doctor.html doivent appeler les deux fragments fragments/header.html et fragments/footer.html