Validation HTML 5 avec Spring MVC et Bean Validation

Logo HTML 5

Cet article explique comment étendre Spring MVC pour générer le code HTML 5 des champs de saisie (input fields) à partir des annotations Bean Validation (JSR 330) apposées sur des Entités ou de simples DTO.

Dans une application web, valider les écrans de saisie côté client permet de donner un retour rapide à l’utilisateur. Avant HTML 5, le développeur web était bien démuni pour implémenter ces contrôles de surface sur le Navigateur. Certes, HTML 4 permettait de spécifier la taille max des champs de saisie (balise maxLength) et leur caractère obligatoire ou non (balise required). Les autres contrôles effectués côté serveur étaient alors bien souvent recodés en JavaScript à l’aide de jQuery, de CSS et de quelques plugins.
Aujourd’hui, HTML 5 se démocratise et le code JavaScript de validation devrait bientôt s’alléger drastiquement. En effet, cette spécification permet de standardiser la validation des champs de saisie côté client. Le développeur a désormais la possibilité de spécifier le type de champs (ex : nombre, date, URL …), des valeurs min et max ou bien encore un pattern de validation à l’aide d’une expression régulière.

Validation HTML 5

Dans l’exemple ci-dessous exploitant les capacités du HTML 5, Google Chrome gère nativement la validation du formulaire et l’affichage du message d’erreur.
Les icones sont obtenus à l’aide d’un style CSS utilisant les pseudo-classes input:required:invalid, input:focus:invalid et input:required:valid.

2014-08-input-spring-mvc-en-html5-email-erreurVoici la représentation HTML de ce formulaire :

<form id="customer" action="/htmlvalidation" method="post">
	<div>
		<label>First Name</label>
		<input id="firstName" type="text" required="required" />
	</div>
	<div>
		<label>Last Name</label>
		<input id="lastName" type="text" required="required" />
	</div>
	<div>
		<label>Address</label>
		<input id="address" type="text" maxlength="20" />
	</div>
	<div>
		<label>City</label>
		<input id="city" type="text" required="required" />
	</div>
	<div>
		<label>Telephone</label>
		<input id="telephone"type="text" maxlength="10" />
	</div>
	<div>
		<label>Email</label>
		<input id="email" type="email" />
	</div>
	<div>
		<label>Website URL</label>
		<input id="website" type="url" />
	</div>
	<div>
		<label>Age</label>
		<input id="age" type="number" max="99" min="18" />
	</div>
	<div>
		<button type="submit">Add Customer</button>
	</div>
</form>

Dans la suite de cet article, nous verrons comment Spring MVC peut générer ce code HTML 5.

Attention toutefois, chaque navigateur implémente différemment cette norme.
Par exemple, sous Google Chrome 36, les champs de type date sont particulièrement aboutis, avec masque de saisie et calendrier ; voir ci-dessous la représentation de la ligne HTML Birthdate: <input type= »date » name= »birthdate »> . Par contre, ni Internet Explorer 11 ni Firefox 31 ne fournissent un tel confort de saisie.

Champs de saisie d'une date HTML 5 sous Google Chrome

 Bean Validation

Dans le monde Java, la spécification Bean Validation (JSR-303 et JSR-349) n’a plus à faire ses preuves. Elle s’est tout d’abord imposée au niveau de la couche de persistance. Ses annotations sont en effet utilisées par JPA pour générer la structure de la base de données et pour valider les données avant d’exécuter les ordres SQL d’insertion et de mise à jour. Bean Validation a ensuite fait son entrée au niveau de la couche de présentation : avec JSF 2 puis dans Spring MVC via l’annotation @Valid.
Enfin, Bean Validation peut également être utilisé en dehors de tout framework, par exemple pour valider les données en entrée d’un web service.

Code HTML 5 généré

Spring MVC permet de binder bi-directionnellement un champ de saisie avec la propriété d’une classe. Cette classe peut être aussi bien une entité métier qu’un simple DTO. En supposant que Bean Validation est utilisé pour valider les données de cette classe côté server, nous allons demander à Spring d’exploiter ces annotations lors de la génération des attributs HTML de la balise <input/> , et ceci via le tag JSP personnalisé <jem:input />  que nous allons développer.

Voici le code HTML 5 que nous aimerions que Spring MVC génère :

Code Java Page JSP HTML 5 généré
@NotEmpty
String firstName;
<jem:input path= »firstName » /> <input id= »firstName » type= »text » required= »required » />
@NotNull
String city;
<jem:input path= »city » /> <input id= »city » type= »text » required= »required » />
@Size(max=40)
String address;
<jem:input path= »address » /> <input id= »address » type= »text » maxlength= »40″ />
@Size(max=40)
String address;
<jem:input path= »address » maxlength= »20″/> <input id= »address » type= »text » maxlength= »20″ />
@Min(value = 18)   @Max(value=99)   Integer age; <jem:input path= »age » /> <input id= »age » type= »number » max= »99″ min= »18″ />
@Email
String email;
<jem:input path= »email » /> <input id= »email » type= »email » />
@URL
String website;
<jem:input path= »website » /> <input id= »website » type= »url » />
Integer birthYear; <jem:input path= »birthYear » /> <input id= »birthYear » type= »number » />

Remarques :

  • Les attributs font parties de la classe Customer. Côté contrôleur web, une instance est ajoutée au modèle Spring MVC de la vue.
  • Le préfixe <jem: permet de distinguer notre balise personnalisée avec la balise input de Spring MVC (<form:input />)
  • Les tags <jem:input />  sont disposés dans le formulaire <form:form modelAttribute= »customer »>

Si besoin est, l’attribut maxlength peut être redéfini manuellement via le tag <jem:input /> .

Mise en œuvre

L’implémentation du tag Html5InputTag interprétant les contraintes Bean Validation demande un peu moins de 200 lignes de code. Elle spécialise la classe org.springframework.web.servlet.tags.form.InputTag de Spring MVC.
Trois méthodes y sont redéfinies :

  1. Avant d’appeler la méthode parent, la méthode writeTagContent analyse la propriété à binder à la recherche de contraintes matérialisées par des annotations Bean Validation. Le résultat est stocké dans une Map et sera utilisé dans les 2 autres méthodes.
  2. En complément des attributs type et value, la méthode writeValue est chargée d’écrire les attributs maxLength, min, max et required à partir des contraintes portées par la propriété à binder.
  3. Enfin, la méthode getType détermine la valeur de l’attribut type en fonction du type de la propriété à binder (ex : Integer) ou des contraintes qu’elle porte.

Pour davantage de détails, voici le code source complet de la classe Html5InputTag:

Extrait de la classe Html5InputTag.java

Cette classe peut être reprise et adaptée en fonction de vos besoins.

Tests unitaires

La classe TestHtml5InputTag teste unitairement chacune des annotations Bean Validation supportés par le tag.
A titre d’exemple, voici la méthode testant le HTML généré à partir de l’annotation @Size :

Extrait de la classe TestHtml5InputTag.java

Intégration manuelle du tag

Pour utiliser cette classe dans une application Spring MVC, il est nécessaire de déclarer le tag correspondant dans un fichier TLD qui sera analysé par le conteneur web à son démarrage. Ce descripteur doit se situer dans le répertoire WEB-INF\tld (pour un WAR) ou META-INF\tld (pour un JAR).

La description du tag reprend exactement celle du tag input de Spring MVC déclaré dans le descripteur META_INF/spring-form.tld du module spring-webmvc. Seule l’implémentation change.

Taglib prête à l’emploi

Pour faciliter l’intégration de ce tag, le projet open source spring-mvc-toolkit propose un taglib. L’URI du taglib est https://javaetmoi.com/core/spring-mvc

Afin de pouvoir l’utiliser sur une application existante, l’ajout de la dépendance maven suivante est nécessaire :

<dependency>
     <groupId>com.javaetmoi.core</groupId>
     <artifactId>spring-mvc-toolkit</artifactId>
     <version>0.1</version>
</dependency>

Lors d’un mvn clean install , le JAR sera téléchargé depuis Maven Central.

Démo

Le projet spring-mvc-toolkit vient avec une application démo mettant en œuvre les différentes fonctionnalités offertes par le projet
La page htmlvalidation.jsp montre comment utiliser le tag Html5InputTag. Remarquez qu’aucun code JavaScript n’est utilisé. Afin d’uniformiser le comportement sur l’ensemble des navigateurs, 2 styles CSS sont appliqués aux pseudo-classes :valid et :invalid pour afficher des icônes à droite du champ de saisie.

Dans le pom.xml de cette application web de démo, le plugin Jetty pour maven est préconfiguré.

Voici la démarche à suivre pour tester la page :

  1. Récupérer le code hébergé sur GitHub :
    git clone git://github.com/arey/spring-mvc-toolkit.git
  2. Construire le projet avec maven :
    cd spring-mvc-tookit mvn clean install
  3. Démarrer Jetty
    cd spring-mvc-toolkit-demo
    mvn jetty:run-war
  4. Accéder à la page de test du tag depuis votre Navigateur :
    http://localhost:8080/htmlvalidation

Conclusion

Cet article aura montré comment étendre les tags JSP de Spring MVC pour ajouter la validation côté client apportée par HTML 5. L’enrichissement du HTML généré par les tags se base sur les contraintes Bean Validation.

A ce jour, la classe Html5InputTag supporte 4 annotations Bean Validation (@Min, @Max, @NotNull et @Size) et 3 annotations spécifiques à Hibernate Validator (@Email, @NotEmpty et @URL).
Le support d’autres annotations pourraient être ajouté. L’annotation @Pattern pourrait par exemple générer l’attribut pattern qui accepte une expression régulière. La difficulté réside dans l’adaptation d’une regex Java en regex JavaScript, ce qui a été fait dans le sens inverse par l’équipe GWT.
Le support des groups Bean Validation pourrait également être ajouté.
Enfin, ce qui a ici été appliqué pour la classe InputTag peut l’être à moindre échelle sur la classe TextAreaTag.

Références :

  1. HTML5 Form Validation Examples
  2. Formulaires HTML5 : placeholder, required, pattern et validation
  3. Classe RegExp.java de GWT

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.