Créer un formulaire simple avec Spring et SimpleFormController

Ce billet décrit l’utilisation d’un SimpleFormController pour la création d’un formulaire simple avec Spring. Je vous conseille d’utiliser SimpleFormController lorsque vous devez créer un formulaire permettant de peupler un objet composé de propriétés simples (boolean, int, String, etc…) . Dans le cas où vous voudriez écrire une applicaiton permettant de peupler des objets un peu plus complexe, je AbstractWizardFormController qui fera l’objet d’un prochain billet.


SimpleFormController possède 5 attributs interressants: 

  1. formView : Il s’agit de la vue à afficher lors de l’édition du formulaire. Cette vue est affichée tant que le formulaire n’est pas validé et submitté.
  2. successView : C’est la vue à afficher après la validation et le submit du formulaire.
  3. commandClass : La classe (java) de l’objet qui servira de commande au formulaire.
  4. commandName : Le nom associé à l’objet command
  5. validator: Il s’agit de la classe de l’objet qui sera en charge de la validation de l’objet command. C’est dans cette classe que l’on fera les validations de surface (format et champs obligatoires).

Pour utiliser SimpleFormController (SFC pour les intimes), la première étape consiste à étendre cette classe et eventuellement redefinir certaines de ses méthodes.
L’implémentation de base est plutôt complète, mais elle ne sert à rien tant que vous n’avez pas redéfini la méthode onSubmit() .

Cette méthode est éxécutée une fois que le formulaire a été bindé, validé et submitté.

Le binding: Cette opération est éxecuté implicitement par SimpleFormController lors du submit du formulaire dans la méthode onBind(). Elle consiste a rechercher dans la JSP pointée par formView les correspondances avec les propriétés de l’object command . Pour cela, elle s’appuie sur le tag qui indique quelle propriété doit être attachée avec quelle valeur contenue dans le formulaire. Ces valeurs sont portées par des champs html input comme des textfield, des radio, des select ou des champs hidden.
Le binding par défaut du SFC reconnait certains types standard comme String, Integer, BigDecimal, Long, etc… . Pour celà, il se base sur la locale de la request et instancie un formatter (NumberFormatter par ex ) capable de parser la valeur. Il est possible de personnaliser le binder de SFC en surchargeant la méthode initBinder() . Cela permet par exemple de parser directement dates ou des types que vous avez vous-même définis.
Lorsque le binding ne peut s’opérer sur les types gérés par le binder, la méthode onBind peuple l’objet BindException errors avec des erreurs typeMismatch.

La validation: Cette opération est éxécutée implicement par SFC après le binding. Pour cela, elle récupère l’objet command et execute sur ce dernier la méthode validate du validator. La méthode validate doit peupler l’objet errors avec des messages d’erreurs spécifiés par l’utilisateur. Il est conseillé d’effectuer les validations de surface dans la méthode validate: Verification des champs obligatoires, vérifications de la taille des données ou de l’ordre des dates.

Le submit: Une fois le binding et la validation passés sans erreurs, SimpleFormController execute la méthode onSubmit . L’implémentaiton par défaut se contente de renvoyer un model contenant l’objet command vers la vue successView. Il est donc conseillé d’effectuer les traitements utilisant l’objet command à cet endroit. On appellera par exemple la méthode de sauvegarde de l’objet command en base, ou on pourra charger une liste ou un résultat en utilisant comme parametre d’entrée l’objet command.

  • La méthode onBindAndValidate()

Je reviens rapidement sur cette méthode. L’implémentation par défaut de cette méthode ne fait rien. Or elle est importante, car elle exécutée une fois que le binding s’est fait correctement et que la validation de surface est passée sans erreur. C’est donc l’endroit idéeal pour effectuer les validations métiers (verification de l’unicité d’une valeur, appel à une base de données pour vérifier la cohérence des données saisies, etc…).
Je conseille d’effectuer ces validations business ici, car de cette facon, elles ne polluent pas le Validator avec des appels à des DAO ou à des services métiers d’une part. D’autre part, la validation business aura accès aux infos contenues dans la request et aux services métiers.

voici un exemple de code:

UtilisateurEditController :
[sourcecode language=”java”]
/**
* Controlleur spring permettant d’editer/ajouter un utilisateur.
*
* @author <a href="mailto:akram.benaissi@gmail.com">Akram BEN AISSI </a>
*/
public class EditUtilisateurController extends SimpleFormController{
private static Logger logger = Logger.getLogger(EditUtilisateurController.class);</code>

<code>private AdministrateurEAIProxyManager proxy;</code>

<code>/**
* Prépare les données du formulaire et affiche la liste complète de ces
* employeurs.
*
* @see org.springframework.web.servlet.mvc.AbstractFormController#formBackingObject(javax.servlet.http.HttpServletRequest)
*/
protected Object formBackingObject(HttpServletRequest request) throws Exception {
logger.debug("Appel de formBackingObject");
/**
* Si l’id de l’utilisateur n’est pas présent on est dans le cas d’ajout
* d’un sourcier.
*/
String id = request.getParameter(ApplicationConfig.ID);
UtilisateurModel utilisateur = new UtilisateurModel();
RoleModel role = new RoleModel();
utilisateur.setRole(role);

if (id != null &amp;&amp; !id.trim().equals("")) {
long idUtilisateur = Long.parseLong(id);
utilisateur = getProxy().trouverUtilisateur(idUtilisateur);
}
request.setAttribute(ApplicationConfig.ID, id);
return utilisateur;
}

/**
* Prépare les données de références de la vue. Ces données sont ensuite
* ajoutées au model pour affichage dans la vue.
*
* @see org.springframework.web.servlet.mvc.SimpleFormController#referenceData(javax.servlet.http.HttpServletRequest)
*/
protected Map referenceData(HttpServletRequest request) throws Exception {
Map data = new HashMap();
data.put("roles", getProxy().rechercherRoles());
return data;
}

/**
* Spring binde les listes en tant que liste de String contenant la value de
* la selection. Or, notre liste d’employeur doit contenir des
* EmployeurModel. Il faut dont la reconstruire à partir des ID des
* employeurs. On le fait dans la méthode onBind car celle ci est appellé
* même lors de l’echec de la validation. Cela permet, entre autre, de
* pouvoir afficher les noms des employeurs déjà sélectionné par
* l’utilisateur.
*
*/
protected void onBind(HttpServletRequest request, Object command) throws Exception {
super.onBind(request, command);
UtilisateurModel utilisateur = (UtilisateurModel) command;
logger.debug("Command: " + command);
List employeursOrIds = utilisateur.getEmployeurs();
List employeurs = getProxy().trouverEmployeurs(employeursOrIds);
utilisateur.setEmployeurs(employeurs);
}

/**
* Effectue le traitement si aucune erreurs n’a eu lieu. Si une erreur
* business ou données à lieu, elle est interceptée et le message d’erreur à
* afficher dans la vue est préparé ici.
*
* @see org.springframework.web.servlet.mvc.BaseCommandController#onBindAndValidate(javax.servlet.http.HttpServletRequest,
* java.lang.Object, org.springframework.validation.BindException)
*/
protected void onBindAndValidate(HttpServletRequest request, Object command, BindException errors) throws Exception {
super.onBindAndValidate(request, command, errors);
if (errors.hasErrors()) {
return;
}

UtilisateurModel user = (UtilisateurModel) request.getSession().getAttribute(ApplicationConfig.USER);
UtilisateurModel utilisateur = (UtilisateurModel) command;
utilisateur.setModificationUser(user.getLogin());
String id = (String) request.getAttribute(ApplicationConfig.ID);
if (id != null &amp;&amp; !id.trim().equals("")) {
utilisateur.setUtilisateurId(Long.parseLong(id));
}
try {
getProxy().corrigerUtilisateur(utilisateur);
logger.debug("Utilisateur corrigé sans erreur");
} catch (DuplicateException e) {
logger.debug("Utilisateur corrigé AVEC ERREUR");
errors.rejectValue("login", "error.already.exists.login");
}
}

/**
* Redirige vers la vue de succès. Les erreurs business sont traitées dans
* onBindAndValidate pour permettre à la fois la validation de surface des
* données puis la validation business.
*
* @see org.springframework.web.servlet.mvc.SimpleFormController#onSubmit(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse, java.lang.Object,
* org.springframework.validation.BindException)
*/
protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object command,
BindException errors) throws RemoteException, WebAppEAIException, SQLException {
String id = (String) request.getAttribute(ApplicationConfig.ID);
List utilisateurs = getProxy().trouverUtilisateurs("");
Map model = new HashMap();
model.put("utilisateurs", utilisateurs);
model.put("form", new RechercheBean());
model.put(ApplicationConfig.ID, id);

String pageParameterName = new ParamEncoder("utilisateurRow").encodeParameterName(TableTagParameters.PARAMETER_PAGE);
String pageParameter = "?" + pageParameterName + "=" + request.getParameter(pageParameterName);
ModelAndView mv = new ModelAndView(new RedirectView(getSuccessView() + pageParameter));
return mv;
}

/**
* @return Returns the proxy.
*/
public AdministrateurEAIProxyManager getProxy() {
return proxy;
}

/**
* @param proxy
* The proxy to set.
*/
public void setProxy(AdministrateurEAIProxyManager administrateurEAIProxyManager) {
this.proxy = administrateurEAIProxyManager;
}

}
[/sourcecode]

Leave a Reply

Your email address will not be published. Required fields are marked *