Nous sommes presque au bout de cette deuxième partie du cours ! Cette dernière aura été très volumineuse et il se peut que vous ayez oublié pas mal de choses depuis votre lecture, ce TP va donc se charger de vous rappeler l'essentiel de ce que nous avons appris ensemble.
Le sujet va porter sur la création d'un formulaire dynamique. Qu'est-ce nous entendons par formulaire dynamique ? Eh bien, un formulaire dont une partie des vérifications est effectuée par le JavaScript, côté client. On peut par exemple vérifier que l'utilisateur a bien complété tous les champs, ou bien qu'ils contiennent des valeurs valides (si le champ « âge » ne contient pas des lettres au lieu de chiffres par exemple).
À ce propos, nous allons tout de suite faire une petite précision très importante pour ce TP et tous vos codes en JavaScript :
Bien, nous pouvons maintenant commencer !
Faire un formulaire c'est bien, mais encore faut-il savoir quoi demander à l'utilisateur. Dans notre cas, nous allons faire simple et classique : un formulaire d'inscription. Notre formulaire d'inscription aura besoin de quelques informations concernant l'utilisateur, cela nous permettra d'utiliser un peu tous les éléments HTML spécifiques aux formulaires que nous avons vus jusqu'à présent. Voici les informations à récupérer ainsi que les types d'éléments HTML :
Information à relever |
Type d'élément à utiliser |
---|---|
Sexe |
|
Nom |
|
Prénom |
|
Âge |
|
Pseudo |
|
Mot de passe |
|
Mot de passe (confirmation) |
|
Pays |
|
Si
l'utilisateur souhaite |
|
Bien sûr, chacune de ces informations devra être traitée afin que l'on sache si le contenu est bon. Par exemple, si l'utilisateur a bien spécifié son sexe ou bien s'il n'a pas entré de chiffres dans son prénom, etc. Dans notre cas, nos vérifications de contenu ne seront pas très poussées pour la simple et bonne raison que nous n'avons pas encore étudié les « regex » à ce stade du cours, nous nous limiterons donc à la vérification de la longueur de la chaîne ou bien à la présence de certains caractères. Bref, rien d'incroyable, mais cela suffira amplement car le but de ce TP n'est pas vraiment de vous faire analyser le contenu mais plutôt de gérer les événements et le CSS de votre formulaire.
Voici donc les conditions à respecter pour chaque information :
Information à relever |
Condition à respecter |
---|---|
Sexe |
Un sexe doit être sélectionné |
Nom |
Pas moins de 2 caractères |
Prénom |
Pas moins de 2 caractères |
Âge |
Un nombre compris entre 5 et 140 |
Pseudo |
Pas moins de 4 caractères |
Mot de passe |
Pas moins de 6 caractères |
Mot de passe (confirmation) |
Doit être identique au premier mot de passe |
Pays |
Un pays doit être sélectionné |
Si
l'utilisateur souhaite |
Pas de condition |
Concrètement, l'utilisateur n'est pas censé connaître toutes ces conditions quand il arrive sur votre formulaire, il faudra donc les lui indiquer avant même qu'il ne commence à entrer ses informations, comme ça il ne perdra pas de temps à corriger ses fautes. Pour cela, il va vous falloir afficher chaque condition d'un champ de texte quand l'utilisateur fera une erreur. Pourquoi parlons-nous ici uniquement des champs de texte ? Tout simplement parce que nous n'allons pas dire à l'utilisateur « Sélectionnez votre sexe » alors qu'il n'a qu'une case à cocher, cela paraît évident.
Autre chose, il faudra aussi faire une vérification complète du formulaire lorsque l'utilisateur aura cliqué sur le bouton de soumission. À ce moment-là, si l'utilisateur n'a pas coché de case pour son sexe on pourra lui dire qu'il manque une information, pareil s'il n'a pas sélectionné de pays.
Vous voilà avec toutes les informations nécessaires pour vous lancer dans ce TP. Nous vous laissons concevoir votre propre code HTML, mais vous pouvez très bien utiliser celui de la correction si vous le souhaitez.
Bien, vous avez probablement terminé si vous lisez cette phrase. Ou bien vous n'avez pas réussi à aller jusqu'au bout, ce qui peut arriver !
Nous pouvons maintenant passer à la correction. Pour ce TP, il vous fallait créer la structure HTML de votre page en plus du code JavaScript ; voici le code que nous avons réalisé pour ce TP :
<!DOCTYPE html>
charset="utf-8"
TP : Un formulaire interactif
id="myForm"
class="form_col"Sexe :
name="sex" type="radio" value="H" Homme
name="sex" type="radio" value="F" Femme
class="tooltip"Vous devez sélectionnez votre sexe
class="form_col" for="lastName"Nom :
name="lastName" id="lastName" type="text"
class="tooltip"Un nom ne peut pas faire moins de 2 caractères
class="form_col" for="firstName"Prénom :
name="firstName" id="firstName" type="text"
class="tooltip"Un prénom ne peut pas faire moins de 2 caractères
class="form_col" for="age"Âge :
name="age" id="age" type="text"
class="tooltip"L'âge doit être compris entre 5 et 140
class="form_col" for="login"Pseudo :
name="login" id="login" type="text"
class="tooltip"Le pseudo ne peut pas faire moins de 4 caractères
class="form_col" for="pwd1"Mot de passe :
name="pwd1" id="pwd1" type="password"
class="tooltip"Le mot de passe ne doit pas faire moins de 6 caractères
class="form_col" for="pwd2"Mot de passe (confirmation) :
name="pwd2" id="pwd2" type="password"
class="tooltip"Le mot de passe de confirmation doit être identique à celui d'origine
class="form_col" for="country"Pays :
name="country" id="country"
value="none"Sélectionnez votre pays de résidence
value="en"Angleterre
value="us"États-Unis
value="fr"France
class="tooltip"Vous devez sélectionner votre pays de résidence
class="form_col"
name="news" type="checkbox" Je désire recevoir la newsletter chaque mois.
class="form_col"
type="submit" value="M'inscrire" type="reset" value="Réinitialiser le formulaire"
Vous remarquerez que de nombreuses
balises<span>
possèdent une classe
nommée.tooltip
. Elles contiennent le texte à afficher lorsque le contenu du
champ les concernant ne correspond pas à ce qui est souhaité.
Nous allons maintenant passer au CSS. D'habitude nous ne vous le fournissons pas directement, mais cette fois il fait partie intégrante de ce TP, donc le voici :
body {
padding-top: 50px;
}
.form_col {
display: inline-block;
margin-right: 15px;
padding: 3px 0px;
width: 200px;
min-height: 1px;
text-align: right;
}
input {
padding: 2px;
border: 1px solid #CCC;
border-radius: 2px;
outline: none; /* Retire les bordures appliquées par certains navigateurs (Chrome notamment) lors du focus des éléments <input> */
}
input:focus {
border-color: rgba(82, 168, 236, 0.75);
box-shadow: 0 0 8px rgba(82, 168, 236, 0.5);
}
.correct {
border-color: rgba(68, 191, 68, 0.75);
}
.correct:focus {
border-color: rgba(68, 191, 68, 0.75);
box-shadow: 0 0 8px rgba(68, 191, 68, 0.5);
}
.incorrect {
border-color: rgba(191, 68, 68, 0.75);
}
.incorrect:focus {
border-color: rgba(191, 68, 68, 0.75);
box-shadow: 0 0 8px rgba(191, 68, 68, 0.5);
}
.tooltip {
display: inline-block;
margin-left: 20px;
padding: 2px 4px;
border: 1px solid #555;
background-color: #CCC;
border-radius: 4px;
}
Notez bien les deux
classes.correct
et.incorrect
: elles
seront appliquées aux<input>
de typetext
etpassword
afin
de bien montrer si un champ est correctement rempli ou non.
Nous pouvons maintenant passer au plus compliqué, le code JavaScript :
// Fonction de désactivation de l'affichage des "tooltips"
function deactivateTooltips() {
var tooltips = document.querySelectorAll('.tooltip'),
tooltipsLength = tooltips.length;
for (var i = 0; i < tooltipsLength; i++) {
tooltips[i].style.display = 'none';
}
}
// La fonction ci-dessous permet de récupérer la "tooltip" qui correspond à notre input
function getTooltip(elements) {
while (elements = elements.nextSibling) {
if (elements.className === 'tooltip') {
return elements;
}
}
return false;
}
// Fonctions de vérification du formulaire, elles renvoient "true" si tout est ok
var check = {}; // On met toutes nos fonctions dans un objet littéral
check['sex'] = function() {
var sex = document.getElementsByName('sex'),
tooltipStyle = getTooltip(sex[1].parentNode).style;
if (sex[0].checked || sex[1].checked) {
tooltipStyle.display = 'none';
return true;
} else {
tooltipStyle.display = 'inline-block';
return false;
}
};
check['lastName'] = function(id) {
var name = document.getElementById(id),
tooltipStyle = getTooltip(name).style;
if (name.value.length >= 2) {
name.className = 'correct';
tooltipStyle.display = 'none';
return true;
} else {
name.className = 'incorrect';
tooltipStyle.display = 'inline-block';
return false;
}
};
check['firstName'] = check['lastName']; // La fonction pour le prénom est la même que celle du nom
check['age'] = function() {
var age = document.getElementById('age'),
tooltipStyle = getTooltip(age).style,
ageValue = parseInt(age.value);
if (!isNaN(ageValue) && ageValue >= 5 && ageValue <= 140) {
age.className = 'correct';
tooltipStyle.display = 'none';
return true;
} else {
age.className = 'incorrect';
tooltipStyle.display = 'inline-block';
return false;
}
};
check['login'] = function() {
var login = document.getElementById('login'),
tooltipStyle = getTooltip(login).style;
if (login.value.length >= 4) {
login.className = 'correct';
tooltipStyle.display = 'none';
return true;
} else {
login.className = 'incorrect';
tooltipStyle.display = 'inline-block';
return false;
}
};
check['pwd1'] = function() {
var pwd1 = document.getElementById('pwd1'),
tooltipStyle = getTooltip(pwd1).style;
if (pwd1.value.length >= 6) {
pwd1.className = 'correct';
tooltipStyle.display = 'none';
return true;
} else {
pwd1.className = 'incorrect';
tooltipStyle.display = 'inline-block';
return false;
}
};
check['pwd2'] = function() {
var pwd1 = document.getElementById('pwd1'),
pwd2 = document.getElementById('pwd2'),
tooltipStyle = getTooltip(pwd2).style;
if (pwd1.value == pwd2.value && pwd2.value != '') {
pwd2.className = 'correct';
tooltipStyle.display = 'none';
return true;
} else {
pwd2.className = 'incorrect';
tooltipStyle.display = 'inline-block';
return false;
}
};
check['country'] = function() {
var country = document.getElementById('country'),
tooltipStyle = getTooltip(country).style;
if (country.options[country.selectedIndex].value != 'none') {
tooltipStyle.display = 'none';
return true;
} else {
tooltipStyle.display = 'inline-block';
return false;
}
};
// Mise en place des événements
(function() { // Utilisation d'une IIFE pour éviter les variables globales.
var myForm = document.getElementById('myForm'),
inputs = document.querySelectorAll('input[type=text], input[type=password]'),
inputsLength = inputs.length;
for (var i = 0; i < inputsLength; i++) {
inputs[i].addEventListener('keyup', function(e) {
check[e.target.id](e.target.id); // "e.target" représente l'input actuellement modifié
});
}
myForm.addEventListener('submit', function(e) {
var result = true;
for (var i in check) {
result = check[i](i) && result;
}
if (result) {
alert('Le formulaire est bien rempli.');
}
e.preventDefault();
});
myForm.addEventListener('reset', function() {
for (var i = 0; i < inputsLength; i++) {
inputs[i].className = '';
}
deactivateTooltips();
});
})();
// Maintenant que tout est initialisé, on peut désactiver les "tooltips"
deactivateTooltips();
Essayer le code complet de ce TP
Les explications vont essentiellement porter sur le code JavaScript qui est, mine de rien, plutôt long (plus de deux cents lignes de code, ça commence à faire pas mal).
Dans notre code HTML nous avons créé des
balises<span>
avec la
classe.tooltip
. Ce sont des balises qui vont nous permettre d'afficher des
bulles d'aide, pour que l'utilisateur sache quoi entrer comme contenu. Seulement, elles sont affichées par défaut et
il nous faut donc les cacher par le biais du JavaScript.
Et pourquoi ne pas les cacher par défaut puis les afficher grâce au JavaScript ?
Si vous faites cela, vous prenez le risque qu'un utilisateur ayant désactivé le JavaScript ne puisse pas voir les bulles d'aide, ce qui serait plutôt fâcheux, non ? Après tout, afficher les bulles d'aide par défaut et les cacher avec le JavaScript ne coûte pas grand-chose, autant le faire… De plus, nous allons avoir besoin de cette fonction plus tard quand l'utilisateur voudra réinitialiser son formulaire.
Venons-en donc au code :
function deactivateTooltips() {
var tooltips = document.querySelectorAll('.tooltip'),
tooltipsLength = tooltips.length;
for (var i = 0; i < tooltipsLength; i++) {
tooltips[i].style.display = 'none';
}
}
Est-il vraiment nécessaire de vous
expliquer ce code en détail ? Il ne s'agit que de cacher tous les éléments qui ont une
classe.tooltip
.
<input>
Il
est facile de parcourir toutes les bulles d'aide, mais il est un peu plus délicat de récupérer celle correspondant à
un<input>
que l'on est actuellement en train de traiter. Si nous
regardons bien la structure de notre document HTML, nous constatons que les bulles d'aide sont toujours placées
après l'<input>
auquel elles correspondent, nous allons donc partir du
principe qu'il suffit de chercher la bulle d'aide la plus « proche » après l'<input>
que
nous sommes actuellement en train de traiter. Voici le code :
function getTooltip(element) {
while (element = element.nextSibling) {
if (element.className === 'tooltip') {
return element;
}
}
return false;
}
Notre fonction prend en argument
l'<input>
actuellement en cours de traitement. Notre
bouclewhile
se charge alors de vérifier tous les éléments suivants
notre<input>
(d'où l'utilisation
dunextSibling
). Une fois qu'un élément avec la
classe.tooltip
a été trouvé, il ne reste plus qu'à le retourner.
Nous allons enfin entrer dans le vif du sujet : l'analyse des valeurs et la modification du style du formulaire en conséquence. Tout d'abord, quelles valeurs faut-il analyser ? Toutes, sauf la case à cocher pour l'inscription à la newsletter. Maintenant que cela est clair, passons à la toute première ligne de code :
var check = {};
Alors oui, au premier abord, cette ligne de code ne sert vraiment pas à grand-chose, mais en vérité elle a une très grande utilité : l'objet créé va nous permettre d'y stocker toutes les fonctions permettant de « checker » (d'où le nom de l'objet) chaque valeur entrée par l'utilisateur. L'intérêt de cet objet est triple :
Nous allons
pouvoir exécuter la fonction correspondant à un champ de cette manière :check['id_du_champ']();
.
Cela va grandement simplifier notre code lors de la mise en place des événements.
Il sera possible d'exécuter toutes les fonctions de « check » juste en parcourant l'objet, ce sera très pratique lorsque l'utilisateur cliquera sur le bouton d'inscription et qu'il faudra alors revérifier tout le formulaire.
L'ajout d'un champ de texte et de sa fonction d'analyse devient très simple si on concentre tout dans cet objet, vous comprendrez très rapidement pourquoi !
Nous n'allons pas étudier toutes les fonctions d'analyse, elles se ressemblent beaucoup, nous allons donc uniquement étudier deux fonctions afin de mettre les choses au clair :
check['login'] = function() {
var login = document.getElementById('login'),
tooltipStyle = getTooltip(login).style;
if (login.value.length >= 4) {
login.className = 'correct';
tooltipStyle.display = 'none';
return true;
} else {
login.className = 'incorrect';
tooltipStyle.display = 'inline-block';
return false;
}
};
Il est très important que vous
constatiez que notre fonction est contenue dans l'indexlogin
de
l'objetcheck
, l'index n'est rien d'autre que l'identifiant du champ
de texte auquel la fonction appartient. Le code n'est pas bien compliqué : on récupère
l'<input>
et la
propriétéstyle
de la bulle d'aide qui correspondent à notre fonction
et on passe à l'analyse du contenu.
Si le contenu remplit bien
la condition, alors on attribue à notre<input>
la
classe.correct
, on désactive l'affichage de la bulle d'aide et on
retournetrue
.
Si le contenu ne remplit pas la condition,
notre<input>
se voit alors attribuer la
classe.incorrect
et la bulle d'aide est affichée. En plus de cela, on renvoie
la valeurfalse
.
Passons maintenant à une deuxième fonction que nous tenions à aborder :
check['lastName'] = function(id) {
var name = document.getElementById(id),
tooltipStyle = getTooltip(name).style;
if (name.value.length >= 2) {
name.className = 'correct';
tooltipStyle.display = 'none';
return true;
} else {
name.className = 'incorrect';
tooltipStyle.display = 'inline-block';
return false;
}
};
Cette fonction diffère de la
précédente sur un seul point : elle possède un argumentid
! Cet
argument sert à récupérer l'identifiant de l'<input>
à analyser.
Pourquoi ? Tout simplement parce qu'elle va nous servir à analyser deux champs de texte différents : celui du nom et
celui du prénom. Puisqu'ils ont tous les deux la même condition, il aurait été stupide de créer deux fois la même
fonction.
Donc, au lieu de faire appel à cette fonction sans aucun argument, il faut lui passer l'identifiant du champ de texte à analyser, ce qui donne deux possibilités :
check['lastName']('lastName');
check['lastName']('firstName');
Cependant, ce fonctionnement pose un problème, car nous étions partis du principe que nous allions faire appel à nos fonctions d'analyse selon le principe suivant :
check['id_du_champ']();
Or, si nous faisons ça, cela veut dire
que nous ferons aussi appel à la fonctioncheck['firstName']()
qui
n'existe pas… Nous n'allons pas créer cette fonction sinon nous perdrons l'intérêt de notre système d'argument sur
la fonctioncheck['lastName']()
. La solution est donc de faire une
référence de la manière suivante :
check['firstName'] = check['lastName'];
Ainsi, lorsque nous appellerons la
fonctioncheck['firstName']()
, implicitement ce sera la
fonctioncheck['lastName']()
qui sera appelée. Si vous n'avez pas
encore tout à fait compris l'utilité de ce système, vous allez voir que tout cela va se montrer redoutablement
efficace dans la suite du code.
La mise en place des événements se décompose en deux parties : les événements à appliquer aux champs de texte et les événements à appliquer aux deux boutons en bas de page pour envoyer ou réinitialiser le formulaire.
Nous allons commencer par les champs de texte. Tout d'abord, voici le code :
var inputs = document.querySelectorAll('input[type=text], input[type=password]'),
inputsLength = inputs.length;
for (var i = 0; i < inputsLength; i++) {
inputs[i].addEventListener('keyup', function(e) {
check[e.target.id](e.target.id); // "e.target" représente l'input actuellement modifié
});
}
Comme nous l'avons déjà fait plus
haut, nous n'allons pas prendre la peine de vous expliquer le fonctionnement de cette boucle et de la condition
qu'elle contient, il ne s'agit que de parcourir les<input>
de
typetext
oupassword
.
En revanche, il va falloir des explications sur les lignes 5 à 7.
Elles permettent d'assigner une fonction anonyme à l'événementkeyup
de
l'<input>
actuellement traité. Quant à la ligne 6, elle fait appel à
la fonction d'analyse qui correspond à l'<input>
qui a exécuté
l'événement. Ainsi, si l'<input>
#login
déclenche
son événement, il appellera alors la fonctioncheck['login']()
.
Cependant, un argument est passé à chaque fonction d'analyse que l'on
exécute. Pourquoi ? Eh bien il s'agit de l'argument nécessaire à la fonctioncheck['lastName']()
,
ainsi lorsque
les<input>
#lastName
et#firstName
déclencheront
leur événement, ils exécuteront alors respectivement les lignes de codes suivantes :
check['lastName']('lastName');
et
check['firstName']('firstName');
Mais là on passe l'argument à toutes les fonctions d'analyse, cela ne pose pas de problème normalement ?
Pourquoi cela en poserait-il un ? Imaginons que
l'<input>
#login
déclenche son
événement, il exécutera alors la ligne de code suivante :
check['login']('login');
Cela fera passer un argument inutile dont la fonction ne tiendra pas compte, c'est tout.
Nous pouvons maintenant aborder l'attribution des événements sur les boutons en bas de page :
(function() { // Utilisation d'une IIFE pour éviter les variables globales.
var myForm = document.getElementById('myForm'),
inputs = document.querySelectorAll('input[type=text], input[type=password]'),
inputsLength = inputs.length;
for (var i = 0; i < inputsLength; i++) {
inputs[i].addEventListener('keyup', function(e) {
check[e.target.id](e.target.id); // "e.target" représente l'input actuellement modifié
});
}
myForm.addEventListener('submit', function(e) {
var result = true;
for (var i in check) {
result = check[i](i) && result;
}
if (result) {
alert('Le formulaire est bien rempli.');
}
e.preventDefault();
});
myForm.addEventListener('reset', function() {
for (var i = 0; i < inputsLength; i++) {
inputs[i].className = '';
}
deactivateTooltips();
});
})();
Comme vous pouvez le constater, nous
n'avons pas appliqué d'événementsclick
sur les boutons mais avons
directement appliquésubmit
etreset
sur
le formulaire, ce qui est bien plus pratique dans notre cas.
Alors concernant notre événementsubmit
, celui-ci va parcourir notre
tableaucheck
et exécuter toutes les fonctions qu'il contient (y
compris celles qui ne sont pas associées à un champ de texte commecheck['sex']()
etcheck['country']()
).
Chaque valeur retournée par ces fonctions est « ajoutée » à la variableresult
,
ce qui fait que si une des fonctions a
renvoyéfalse
alorsresult
sera
aussi àfalse
et l'exécution de la
fonctionalert()
ne se fera pas.
Concernant notre événementreset
,
c'est très simple : on parcourt les champs de texte, on retire leur classe et ensuite on désactive toutes les bulles
d'aide grâce à notre fonctiondeactivateTooltips()
.
Voilà, ce TP est maintenant terminé.