Les erreurs dans les formulaires

Ce document propose une démarche pour gérer les erreurs de l’utilisateur dans les formulaires. Il explique comment homogénéiser l’affichage des erreurs si les traitements s’effectuent sur le client ou sur le serveur.
Par Philippe PRADOS - 2001
www.prados.fr
La technologie HTML permet à un utilisateur d’entrer des informations dans des formulaires, et les envoyer au serveur. Celui-ci peut alors les prendre en compte, et doit retourner une nouvelle page pour afficher les résultats. Comment gérer les erreurs de l’utilisateur dans le formulaire ?
Il y a deux démarches principales :
- Gestion sur le poste du client à l’aide de script
- Gestion sur le serveur
Gestion cliente
Avant de soumettre les champs du formulaire au serveur, il est possible de vérifier certaines règles de gestions simples. Par exemple, il est facile de vérifier si un champ est valorisé, s’il est numérique, etc. Toutes ces vérifications seront effectuées dans le navigateur. Cela évite de solliciter le serveur pour des erreurs simples.
Comment présenter à l’utilisateur les messages d’erreurs ? Les scripts permettent d’afficher une boîte de dialogue avec un message alert(). Cela permet de signaler à l'utilisateur son erreur. Il peut alors y remédier avant de soumettre à nouveau le formulaire au serveur.
Gestion serveur
Lorsqu’un formulaire est soumis au serveur, celui-ci peut effectuer des vérifications plus complexes. Il peut interroger la base de données pour vérifier la valeur d’un champ, effectuer des calculs pour vérifier la cohérence d’une donnée, etc. Comment présenter les erreurs à l’utilisateur ?
L’architecture HTTP impose au serveur de retourner une page en réponse à la soumission d’un formulaire.
<form action="Analyse.html">
…
</form>
Le serveur doit retourner la page Analyse.html en y indiquant les erreurs éventuelles. Le client devra retourner à la page précédente, celle du formulaire, pour y remédier.
Cette approche est simple à développer, mais n’est pas très cohérente pour l’utilisateur. Suivant si le test est effectué sur le client ou sur le serveur, l’erreur est signalée par deux techniques différentes : une boîte de dialogue ou une nouvelle page. De plus, l’affichage d’une page d’erreur n’est pas très agréable. L’utilisateur doit retourner à la page précédente pour la corriger.
Comment homogénéiser les deux approches ?
Formulaire incorrect
Le serveur doit retourner une page à l’utilisateur. Il peut retourner à nouveau la page de formulaire en y ajoutant un script afin d’afficher dans une boîte de dialogue le message d’erreur. Le script est lancé automatiquement après le chargement de la page.
<body onload="erreur(1)">
…
</body>
Lorsque l’utilisateur soumet le formulaire, il ne s’aperçoit pas qu’il a rechargé la même page. Il constate son erreur comme si elle avait été détectée avant la soumission.
Pour faire cela, il faut que la soumission indique la même page que pour le formulaire.
<form action="Formulaire.html" method="post">
…
</form>
Ainsi, tant que le formulaire n’est pas correct, l’utilisateur reste sur la même URL.
Value
Cette approche est intéressante, mais il ne faut pas que l’utilisateur perde les quelques informations valides dans son formulaire d’origine lors de la soumission. Par exemple, si le formulaire lui demande son nom et son prénom, et qu’il ne renseigne que son prénom, il ne faut pas perdre le prénom lors de la réception de la nouvelle version du formulaire. Pour résoudre cela, il faut ajouter un script d’initialisation des champs du formulaire avec les données valides. Le script se charge d’initialiser tous les champs valides du formulaire. Il est appelé après le chargement du formulaire.
function initForm()
{
formulaire.nom.value="Porte";
formulaire.prenom.value="bill";
formulaire.age.value="18";
}
Formulaire correct
Que faire lorsque le formulaire est correct ? Qu’il n’a pas d’erreurs ? Il y a plusieurs stratégies.
Calcul simple
Si la page de résultat n’a pas besoin des informations du formulaire ou seulement d’une synthèse simple de l’analyse, il est possible de retourner une demande de relocation (erreur 302 ). L’URL utilisé pour la relocation peut être enrichie de paramètres. Par exemple, s’il est nécessaire de connaître le numéro du client pour afficher la page de résultat, l’analyse du formulaire retourne :
HTTP/1.0 302 Relocation
Location: /Analyse.html?id=1234
Ainsi, la page Analyse.html peut être calculée. Le numéro du client est retourné en analysant les champs du formulaire.
Calcul complexe
Si le calcul de la page Analyse.html nécessite plus d’information qu’un simple numéro de client, il est nécessaire de communiquer les informations disponibles lors de l’analyse du formulaire à cette page. Pour cela, il y a deux approches : le transfert et la relocation.
La page Formulaire.html peut, si tous les champs sont corrects, transférer la demande du calcul à la page Analyse.html. Cela s’effectue à l’intérieur du serveur. Le traitement est géré par la page Analyse.html, mais pour le client, il s’agit du calcul de la page Formulaire.html. L’URL Formulaire.html retourne le contenu de la page Analyse.html. Cette approche n’est pas très propre, car la page Formulaire.html peut retourner deux résultats très différents : le formulaire et le résultat de l’analyse. La même URL référence deux documents différents.
Pour éviter cela, il faut retourner une demande de relocation. Cela entraîne une nouvelle requête de la part du navigateur.

Comment transmettre les informations disponibles de la page Formulaire.html à la page Analyse.html ? Il est nécessaire d’utiliser une session pour l’utilisateur. La page de formulaire, après vérification, construit un objet regroupant toutes les informations pertinentes pour l’analyse. Cet objet est mémorisé dans la session de l’utilisateur. La page d’analyse récupère cet objet pour effectuer les calculs nécessaires. Il s’agit bien de deux traitements distincts : la vérification du formulaire et le traitement de celui-ci.
Dans une architecture client/léger, un objet doit mémoriser l’état du client. Cet objet sera valorisé par la page Formulaire.html et utilisé par la page Analyse.html.
Architecture
Comment faciliter la gestion de cette approche ? La page doit effectuer plusieurs étapes :
- Récupérer les informations du formulaire
- Sélectionner et lancer le (pre-)traitement
- Si la vérification est correcte, passer la main à la page de traitement
- Sinon, ajouter l’affichage de l’erreur sur le client. Construire la page du formulaire en initialisant les différentes valeurs.
Pour faire cela, une démarche classique consiste à utiliser un objet activité qui récupère toutes les informations du formulaire. Une méthode permet alors de vérifier la validité des informations. Si une ou plusieurs erreurs sont détectées, un code d’erreur est retourné. En pseudo code, cela donne :
act=creation d’un objet "activité"
si (requête.mode=="POST") alors
debut
act.nom = requête.formulaire("nom")
act.prenom = requête.formulaire("prenom")
act.age = requête.formulaire("age")
erreur = act.vérification()
fin
Si la demande de la page s’effectue à l’aide d’une requête HTTP de type GET, il s’agit de l’initialisation de la page. Les valeurs par défauts des propriétés de l’activité serviront à initialiser le formulaire.
Il est à noter que la vérification ne demande pas de paramètre. En effet, toutes les informations sont disponibles dans les propriétés de l’objet. L’étape de valorisation des propriétés de l’activité à partir des champs du formulaire peut être automatisée. Le traitement vérification() doit vérifier toutes les règles de gestion. Il peut se limiter à faire cela car le calcul effectif de la page de résultat sera effectué dans une autre page.
En cas d’erreur ou lors de la première invocation à l’aide de la méthode GET, le calcul de la page consiste à produire le code HTML en valorisant les valeurs par défauts à l’aide des propriétés de l’activité. Il faut ajouter l’invocation de l’erreur lorsque la page sera totalement chargée.
<script>
...
function autoStart()
{
initForm();
ShowError(<%=erreur%>);
}
</script>
<body
onload="autostart()">
...
<form …
Nom :<input name="nom"><br>
Prénom :<input name="prénom"><br>
Age :<input name="age"><br>
…
</form>
</body>
</html>
S’il n’y a pas d’erreur, l’activité est mémorisée dans la session, et le traitement renvoie sur une autre page.
S’il n’y a pas d’erreur alors
debut
session("activité")=obj;
réponse.relocation("Analyse.html")
fin
La page s’occupant de l’analyse proprement dite, récupère l’activité avant de calculer la page.
act=session("activité")
act.analyse()
Puis, elle affiche le résultat.
<body>
Bonjour <%= act.nom %> <% = act.prenom %>,
vous avez <%= act.solde %> € sur votre compte.
</body>
Utiliser une activité pour mémoriser les champs du formulaire et pour vérifier les règles de gestion permet de séparer la présentation du traitement. Il est possible de modifier la présentation ou le média de diffusion, sans avoir à modifier l’activité. Par exemple, cette architecture peut être utilisée pour présenter la page en différentes langues, pour diffuser l’information en XML ou en WML pour les téléphones portables. Les démarches à effectuer pour chaque média sont similaires. Toute la logique est présente dans l’objet activité.
Cette architecture permet d’uniformiser la gestion des erreurs de l’utilisateur. Les vérifications peuvent s’effectuer sur le poste du client ou sur le serveur. Le message d’erreur utilisera une ergonomie similaire.
Il est important, pour des raisons de sécurité, de vérifier à nouveau les informations transmises au serveur. Même si un test en local a été effectué, un pirate peut facilement envoyer une donnée erronée. Il est indispensable d’effectuer une double vérification pour tous les champs des formulaires : une dans le navigateur et une autre sur le serveur. Cette architecture facilite la gestion de cette double vérification.
Annexe
JSP
Voici un exemple d’implantation de cette architecture à l’aide de page JSP.
Le document Formulaire.jsp :
<jsp:useBean id="activity"
class="activity.Activity"
scope="session"/>
<%
/*
Dans cet exemple, un formulaire propose à l'utilisateur d'entrer son nom et son prénom.
La page vérifie que les deux champs soient renseigné. L'existence d'un nom est vérifié
par un script exécuté sur le client. L'existence d'un prénom est vérifié par
un script présent sur le serveur. Dans les deux cas d'erreurs,
l'utilisateur obtient un message similaire. Si les deux champs
sont renseignés, un nouvelle page est affichée.
*/
/**
* The current error state
**/
int error=0;
/**
* If the request is POST, it's not the first request but the submit.
* The update the properties and check the preconditions.
* If all check is ok, redirect the URL to another URL.
**/
if (request.getMethod().equals("POST"))
{
// Update activity
//----------------------------
activity.setName(request.getParameter("name"));
activity.setFirstname(request.getParameter("firstname"));
activity.setAge(request.getParameter("age"));
//----------------------------
error=activity.check();
if (error==0)
{
// If no error, redirect to another URL.
response.sendRedirect("/ErrorForm/Analyse.jsp");
}
}
%>
<HTML>
<HEAD>
<TITLE>Error
in form</TITLE>
<script>
<!--
/**
* Init the form with the defaut or the current values.
**/
function initForm()
{
//----------------------------
formulaire.name.value ="<%=
activity.getName() %>";
formulaire.firstname.value="<%=
activity.getFirstname() %>";
formulaire.age.value ="<%=
activity.getAge() %>";
//----------------------------
}
/**
* Show an alert with the error.
*
* @param err The number of the error.
* @return false.
**/
function ShowError(err)
{
var msg=null;
switch (err)
{
case 1 :
msg="Le nom doit être renseigné";
formulaire.name.focus();
formulaire.name.select();
break;
case 2 :
msg="Le prenom doit être renseigné";
formulaire.firstname.focus();
formulaire.firstname.select();
break;
case 3 :
msg="L'age doit être renseigné";
formulaire.age.focus();
formulaire.age.select();
break;
case 4 :
msg="L'age doit être numérique";
formulaire.age.focus();
formulaire.age.select();
break;
}
if (err!=0) alert(msg);
return false;
}
/**
* Check the precondition before to submit the form.
*
* @return true if all is right.
**/
function VerifSubmit()
{
if (formulaire.name.value=="")
return ShowError(1);
/*
if (formulaire.firstname.value=="")
return ShowError(2);
*/
return true;
}
/**
* Init the page. Init the form and show the error.
**/
function autoStart()
{
initForm();
ShowError(<%=error%>);
}
// -->
</script>
</HEAD>
<BODY onLoad="autoStart()">
<FORM action="Formulaire.jsp" name="formulaire" method="POST"
onsubmit="return VerifSubmit();">
Name : <INPUT type="text" name="name" tabindex="1"><br>
firstname : <INPUT type="text" name="firstname" tabindex="2"><br>
Age : <INPUT type="text" name="age" tabindex="3">
<p>
<INPUT type="submit" value="Submit">
<INPUT type="reset" value="Reset">
</p>
</FORM>
</BODY>
</HTML>
Le document Analyse.jsp :
<jsp:useBean id="activity" class="activity.Activity"
scope="session"/>
<HTML>
<HEAD>
<TITLE>Analyse</TITLE>
</HEAD>
<BODY>
Hello
<%=activity.getName()
%> <%=activity.getFirstname()
%>.<br>
Age
<%=activity.getAge()
%>.
</BODY>
</HTML>
et la classe java de l’activité :
package activity;
public class Activity
{
private java.lang.String fieldAge = new String();
private java.lang.String fieldName = new String();
private java.lang.String fieldFirstname = new String();
public java.lang.String getAge()
{
return fieldAge;
}
public java.lang.String getFirstname()
{
return fieldFirstname;
}
public java.lang.String getName()
{
return fieldName;
}
public void setAge(java.lang.String age)
{
fieldAge = age;
}
public void setFirstname(java.lang.String firstname)
{
fieldFirstname = firstname;
}
public void setName(java.lang.String name)
{
fieldName = name;
}
public int check()
{
if (getName().length()==0) return 1;
if (getFirstname().length()==0) return 2;
if (getAge().length()==0) return 3;
try
{
Integer.parseInt(getAge());
} catch (NumberFormatException x)
{
return 4;
}
return 0;
}
}
ASP
Voici un exemple d’implantation de cette architecture à l’aide de page ASP.
Le document Formulaire.asp :
<%@ LANGUAGE="JScript" %>
<%
/*
Dans cet exemple, un formulaire propose à l'utilisateur d'entrer son nom
et son prénom.
La page vérifie que les deux champs soient renseigné. L'existence d'un nom est vérifié
par un script exécuté sur le client. L'existence d'un prénom est vérifié par
un script présent sur le serveur. Dans les deux cas d'erreurs,
l'utilisateur obtient un message similaire. Si les deux champs
sont renseignés, un nouvelle page est affichée.
*/
/**
* Bad state.
**/
function internalError()
{
Response.Redirect("Error.html");
}
/**
* Create or return the current activity.
*
* @return The activity
**/
function getActivity()
{
var activity;
if (Request.ServerVariables("REQUEST_METHOD")!="POST")
{
// Init controler
activity=new Object();
//----------------------------
activity.name=""; // Default value
activity.firstname=""; // Default value
activity.age="";
//----------------------------
Session("activity")= activity;
}
else
{
// Update activity
activity=Session("activity");
if (activity==null)
internalError();
}
return activity;
}
/**
* Check the pre-condition for all the properties in the activity
*
* @return Zero if no error. Integer for the number of error.
**/
function activity_check(act)
{
//----------------------------
if (act.name =="") return 1;
if (act.firstname=="") return 2;
if (act.age=="") return 3;
if (isNaN(act.age)) return 4;
//----------------------------
return 0;
}
/**
* The current activity.
**/
var activity=getActivity();
/**
* The current error state
**/
var error=0;
/**
* If the request is POST, it's not the first request but the submit.
* The update the properties and check the preconditions.
* If all check is ok, redirect the URL to another URL.
**/
if (Request.ServerVariables("REQUEST_METHOD")=="POST")
{
// Update activity
//----------------------------
activity.name =Request.Form("name");
activity.firstname=Request.Form("firstname");
activity.age =Request.Form("age");
//----------------------------
error= activity_check(activity);
if (!error)
{
// If no error, redirect to another URL.
Response.Redirect("Analyse.asp");
}
}
%>
<HTML>
<HEAD>
<TITLE>Error in form</TITLE>
<script>
<!--
/**
* Init the form with the defaut or the current values.
**/
function initForm()
{
//----------------------------
formulaire.name.value ="<%=activity.name %>";
formulaire.firstname.value="<%=activity.firstname %>";
formulaire.age.value ="<%=activity.age %>";
//----------------------------
}
/**
* Show an alert with the error.
*
* @param err The number of the error.
* @return false.
**/
function ShowError(err)
{
var msg=null;
switch (err)
{
case 1 :
msg="Le nom doit être renseigné";
formulaire.name.focus();
formulaire.name.select();
break;
case 2 :
msg="Le prenom doit être renseigné";
formulaire.firstname.focus();
formulaire.firstname.select();
break;
case 3 :
msg="L'age doit être renseigné";
formulaire.age.focus();
formulaire.age.select();
break;
case 4 :
msg="L'age doit être numérique";
formulaire.age.focus();
formulaire.age.select();
break;
}
if (err!=0) alert(msg);
return false;
}
/**
* Check the precondition before to submit the form.
*
* @return true if all is right.
**/
function VerifSubmit()
{
if (formulaire.name.value=="")
return ShowError(1);
/*
if (formulaire.firstname.value=="")
return ShowError(2);
*/
return true;
}
/**
* Init the page. Init the form and show the error.
**/
function autoStart()
{
initForm();
ShowError(<%=error%>);
}
// -->
</script>
</HEAD>
<BODY onLoad="autoStart()">
<FORM action="Formulaire.asp" name="formulaire" method="POST"
onsubmit="return VerifSubmit();">
Name : <INPUT type="text" name="name" tabindex="1"><br>
firstname : <INPUT type="text" name="firstname" tabindex="2"><br>
Age : <INPUT type="text" name="age" tabindex="3">
<p>
<INPUT type="submit" value="Submit">
<INPUT type="reset" value="Reset">
</p>
</FORM>
</BODY>
</HTML>
Le document Analyse.asp :
<%@ LANGUAGE="JScript" %>
<%
/**
* Return the current activity.
*
* @return The activity
**/
function getActivity()
{
return Session("activity");
}
// Get the current activity
var activity=getActivity();
%>
<HTML>
<HEAD>
<TITLE>Analyse</TITLE>
</HEAD>
<BODY>
Hello <%=activity.name
%> <%=activity.firstname
%>.<br>
Age <%=activity.age
%>.
</BODY>
</HTML>
