vendredi 26 mai 2023

Thymeleaf et htmx

Thymeleaf est un moteur de template serveur régulièrement utilisé le monde Java, principalement avec le framework Spring. Dans cet article, nous présentation quelques architectures possibles avec ce framework ainsi que l'utilisation de Htmx afin de rendre les applications thymeleaf plus dynamiques.

 

 Architecture d'application

Voici un ensemble d'architecture varié qu'il est possible de faire avec Thymeleaf

 
La première étant la plus simple et la plus utilisée dans les exemples montrés sur la toile.
J'ai pu expérimenter la cinquième. Le mvc était utilisé pour construire les interfaces utilisateurs, par contre toutes les opérations de suppression, de mise à jour ou de création de données utilisaient une architecture rest. Le javascript était utilisé afin de reloader la page.

C'est une solution simple et rapide à mettre en place face à une solution SPA avec Angular, React ou bien Vue.

HDA

L'augmentation du temps de développement, la complexité des frameworks, courbe d'apprentissage élevé, maintenance onéreuse ont fait reculé certains.

React à htmx

 

Hypermedia Driven Application combine la facilité des multiples pages application (mpa) avec l'expérience utilisateur des single page application (spa). 

Une panoplie de librairie existe pour apporter l'expérience utilisateur à des frameworks orientés serveur.

unpoly, htmx, alpine, hyperscript... permettent via l'ajout de mot clé dans le html de rendre des applications utilisant des framework serveurs: jsf, jsp, thymeleaf... plus dynamique, d'éviter de charger la page au complet..

Htmx

Htmx est une petite librairie qui donne accès à ajax, css, sse et websocket via des attributs en html.

Cette librairie  htmx-spring-boot-thymeleaf facilite l'utilisation de htmx avec spring boot et thymeleaf. Elle n'est pas obligatoire.

Vidéo d'une présentation de thymeleaf avec htmx

https://youtu.be/okCdaBTQsik

Une autre présentation

https://www.youtube.com/watch?v=38WAVRfxPxI

Une liste d'exemple est disponible via ce lien.

Éviter le rafraîchissement de la page 

Notre exemple est un template ayant un fragment pour les css, un autre pour le menu et un autre pour le javascript.
 
index.html
<!DOCTYPE html>
<html lang="en">
<head th:replace="~{fragments/css :: headcss}">
<meta charset="UTF-8">
<title>test</title>
</head>
<body class="container">
<div th:replace="~{fragments/navbarmenu :: menu}"/>

<section id="main" class="fade-me-out">

<div id="modals-here"></div>
</section>

<div th:replace="~{fragments/jsscripts :: jsscripts}"/>

</body>
</html>

 

 Fragment pour le css

<!DOCTYPE html>
<html lang="en">
<head th:fragment="headcss">

<title>Generic form UI</title>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" th:href="@{/css/bootstrap.min.css}" href="/css/bootstrap.min.css" />
<link rel="stylesheet" type="text/css" th:href="@{/css/datepicker-bs5.min.css}" href="/css/datepicker-bs5.min.css" />
</head>
<body>

</body>
</html>


Fragment pour le javascript

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>js scripts</title>
</head>
<body>
<div th:fragment="jsscripts">

<script type="text/javascript" th:src="@{/js/genericui.js}" src="/js/genericui.js"></script>
<script type="text/javascript" th:src="@{/js/bootstrap.bundle.min.js}" src="/js/bootstrap.bundle.min.js"></script>
<script type="text/javascript" th:src="@{/js/datepicker-full.min.js}" src="/js/datepicker-full.min.js"></script>

<script type="text/javascript" th:src="@{/js/htmx.min.js}" src="/js/htmx.min.js"></script>
<script type="text/javascript" th:src="@{/js/inputmask.min.js}" src="/js/inputmask.min.js"></script>

<script type="text/javascript" th:src="@{/js/bootstrap-validation.js}" src="/js/bootstrap-validation.js"></script>

<script type="text/javascript">
document.addEventListener('htmx:afterRequest', function(evt) {

Inputmask().mask(document.querySelectorAll("input"));

const matchesDate = document.getElementsByClassName("date1");

for (let i = 0; i < matchesDate.length; i++) {
const datepicker = new Datepicker(matchesDate[i], {
buttonClass: 'btn',
});
}

});

</script>
</div>
</body>
</html>

Fragment pour le menu

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>NavBar</title>
</head>
<body>

<nav th:fragment="menu" class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" th:href="@{/index2}">Norenda</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" th:href="@{/index}">Index</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" hx-push-url="true" hx:get="@{/modal}" hx-target="#main" hx-swap="innerHTML" hx-trigger="click">Modal</a>
</li>
<li class="nav-item">
<a class="nav-link" hx-push-url="true" hx:get="@{/success}" hx-target="#main" hx-swap="innerHTML swap:1s" hx-trigger="click">Succes</a>
</li>
<li class="nav-item">
<a class="nav-link" hx-push-url="true" hx:get="@{/editor}" hx-target="#main" hx-swap="innerHTML swap:1s" hx-trigger="click">Editeur</a>
</li>
</ul>
<form class="d-flex" role="search" hx:post="@{/search}" hx-target="#main" hx-swap="innerHTML">
<input class="form-control me-2" type="search" placeholder="Recherche" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Recherche</button>
</form>
</div>
</div>
</nav>

</body>
</html>

 

Les deux premiers liens font un chargement complet de la page. 
Le troisième lien affiche une modale
Le quatrième lien affiche un message 
Le cinquième lien affiche un formulaire.

Mis à part les deux premiers liens, il n'y a aucun chargement complet de la page. Il y a remplacement d'une zone par le fragment retourné du serveur.

Un des liens propose une animation.
 
Pour éviter de faire un chargement complet de la page, il faut utiliser certains mots clés.

On mentionne ici que get est fait sur le url mentionnée
hx:get="@{/editor}"

On mentionne ici quel sera la cible du fragment retourné, où ira le contenu
hx-target="#main" 

On mentionne ici qu'on remplace le contenu de la cible par celui du fragment, il serait possible de l'ajouter avant, après
hx-swap="innerHTML"

 

Animation

Quelques animations sont disponibles tels que des fade in, fade out. Il est aussi possible d’utiliser des css pour créer ses propres animations.

Conclusion

Htmx peut vous permettre de rendre encore plus dynamique votre application qui utilise un framework orienté serveur à une fraction du prix en termes de temps, argent et apprentissage. Pour encore plus de fonctionnalité, il faut regarder unpoly.


Vous pouvez télécharger les sources du projet ici.

Aucun commentaire:

Enregistrer un commentaire