Ninja Tips 3 - [hidden] et Bootstrap's help-block
Comme tout le monde, vous développez des applications de gestion (ça se saurait si on pouvait gagner sa vie en développant des jeux vidéo). Et comme vous n’êtes pas très fort en front design, vous bénissez les framework CSS comme Bootstrap, qui vous permettent de capitaliser plein de bonnes pratiques qui vous dépassent, et d’avoir un rendu plutôt joli sans trop réfléchir. Ça donne un rendu certes pas très original (Bootstrap est le nouveau moche disent certains), mais ça contribue grandement à votre productivité. Ne culpabilisez pas, j’en suis là aussi (on doit être de la même génération de vieux).
Vous voilà donc à devoir coder votre formulaire de login en Angular et Bootstrap. Le template HTML, s’il utilise un formulaire Angular piloté par le modèle plutôt que piloté par le template, pourrait ressembler à cela :
<form (ngSubmit)="login()" [ngFormModel]="loginForm">
<div class="form-group">
<label for="username">Login</label>
<input id="username" type="text"
class="form-control" ngControl="username" required>
</div>
<div class="form-group">
<label for="password">Mot de passe</label>
<input id="password" type="password"
class="form-control" ngControl="password" required>
</div>
<button type="submit" class="btn btn-primary">
Me connecter
</button>
</form>
Bravo, vous n’avez pas oublié l’attribut required
sur les deux éléments <input>
,
car ces deux champs sont bien obligatoires. Les navigateurs modernes devraient
même empêcher de soumettre le formulaire si ces deux champs ne sont pas saisis.
Mais un peu de feedback utilisateur ne ferait pas de mal pour l’ergonomie.
Alors vous voulez ajouter un petit texte d’aide, pour préciser que ces champs sont obligatoires.
Bootstrap propose une classe .help-block
,
dont c’est justement le propos :
<div class="form-group">
<label for="password">Mot de passe</label>
<input id="password" type="password"
class="form-control" ngControl="password" required>
<span class="help-block">
Le mot de passe est obligatoire
</span>
</div>
Mais dès que ce formulaire est affiché, ces messages sauteront aux yeux de l’utilisateur, alors qu’il n’a encore rien saisi. Chez Ninja Squad on aime bien que les messages ne s’affichent qu’une fois que l’utilisateur a commencé à saisir. Vous pouvez alors tirer bénéfice des possibilités surnaturelles de validation offertes par un formulaire Angular :
<div class="form-group"
[ngClass]="{
'has-error': password.dirty
&& !password.valid }">
<label for="password">Mot de passe</label>
<input id="password" type="password"
class="form-control" ngControl="password">
<span class="help-block"
[hidden]="password.pristine
|| !password.hasError('required')">
Le mot de passe est obligatoire
</span>
</div>
<button type="submit" class="btn btn-primary" [disabled]="!loginForm.valid">
Me connecter
</button>
Qu’avons-nous mis en œuvre ici ?
- On applique grâce à la directive
ngClass
la classe Bootstrap.has-error
sur ladiv.form-group
si le champpassword
:- est
dirty
, c’est à dire modifié par l’utilisateur ; - a une erreur de validation.
- est
- On n’active le bouton de soumission que si le formulaire est valide, grâce à la propriété
disabled
du DOM. - On masque le
span.help-block
grâce à l’attribut HTML 5 globalhidden
, si :- le champ est
pristine
, c’est à dire vierge de toute modification par l’utilisateur (l’état lors de l’affichage initial) ; - ou si le champ n’a aucune erreur
'required'
de validation.
- le champ est
Plutôt cool. Sauf que… ça ne marche pas complètement.
En l’état, le span.help-block
sera toujours affiché, même quand le champ est vierge, même quand il n’a pas d’erreur.
Ce n’est pas une erreur dans la condition. Ce n’est pas un bug de votre navigateur qui ne supporterait pas cet attribut HTML 5. Ce n’est pas non plus un bug de binding d’Angular sur cet attribut HTML 5. Ce problème m’a coûté quelques bonnes minutes de sueurs froides. Ce pourquoi je voulais vous partager ce ninja tip, aussi anecdotique soit-il.
En fait, le problème se situe dans le comportement de l’attribut HTML 5 global hidden
, et les styles apportés par la classe Bootstrap .help-block
. La documentation de hidden
explique :
changing the value of the CSS
display
property on an element with thehidden
attribute overrides the behavior.
Ce qui signifie pour les auvergnats :
modifier la valeur de la propriété CSS
display
sur un élement avec l’attributhidden
surcharge son comportement.
Et en l’occurrence, la classe help-block
apporte entre autre le style
display: block;
(source)…
Voilà pourquoi votre texte d’aide à l’utilisateur apparaît toujours alors que votre utilisation de la validation Angular est parfaite.
OK, cool. Et maintenant on fait quoi ?
Et bien soit on enlève la classe help-block
(mais on perd le style apporté), soit on réécrit son template pour ne plus faire du binding sur l’attribut hidden
. Et la directive ngIf
arrive alors à la rescousse :
<span class="help-block"
*ngIf="password.dirty
&& password.hasError('required')">
Password is required
</span>
Et voilà !
Il nous a fallu inverser la condition initialement placée dans [hidden]="..."
(mais c’est de l’algèbre de Boole niveau CP), et on a remplacé le test de pristine
par un test de dirty
, qui sont les opposés.
Mise à jour du 12/05/2016 : solution alternative.
Thomas Queste nous suggère dans les commentaires de la version anglaise de cet article une autre solution vue sur StackOverflow : placer le message d’erreur dans son propre
span
à l’intérieur duspan.help-block
.
Merci d’avoir suivi ce long cheminement pour me permettre de vous expliquer cette anecdote, je me sens mieux d’avoir pu vider mon sac. Vous pouvez reprendre une activité normale (comme la lecture de notre ebook sur Angular par exemple).