Question 1Si tu tapes trois caractères dans un champ texte puis que tu appuies sur Tab pour passer au suivant, combien de fois input et change se déclenchent-ils ?
Les formulaires — valeurs saisies et submit
Lire les champs avec value et checked, réagir aux événements input et change, annuler un envoi avec preventDefault et rassembler les champs avec FormData.
Les clics se gèrent avec addEventListener, mais un formulaire demande en plus de lire les valeurs tapées dans ses champs et de les vérifier avant l'envoi. Au clic sur le bouton d'envoi, le navigateur charge une nouvelle page : il faut donc aussi un moyen d'empêcher l'envoi tant que la vérification n'est pas terminée.
Cet article présente value, qui lit la valeur d'un champ, et l'événement submit, déclenché à l'envoi d'un formulaire.
Lire les valeurs saisies — value et checked
Dans un formulaire de commande (élément form), tu veux lire la quantité saisie et calculer le prix total. Un élément input n'a pas de balise fermante et ne peut contenir ni éléments enfants ni texte : lire son textContent ne renvoie donc qu'une chaîne vide.
Les champs sont des éléments input (leur attribut type fixe le genre de champ) ou des éléments textarea pour le texte sur plusieurs lignes. Le contenu d'un champ se lit avec sa propriété value, qui donne la valeur actuelle du champ sous forme de chaîne et permet aussi de la modifier, comme quantity.value ; l'état d'une case à cocher se lit avec checked. L'attribut value écrit dans le HTML est la valeur initiale à l'ouverture de la page : taper dans le champ ne le modifie pas.
document.body.innerHTML = `<form id="order-form"><input id="quantity" type="number" value="1"><input id="gift-wrap" type="checkbox"></form>`;
const quantity = document.querySelector("#quantity");
const giftWrap = document.querySelector("#gift-wrap");
// On fait comme si l'utilisateur avait tapé 3 et coché l'emballage cadeau
quantity.value = "3";
giftWrap.checked = true;
// Même dans un champ numérique, value est une chaîne : on la convertit avant de calculer
console.log(quantity.value + 1); // 31
console.log(Number(quantity.value) * 1200); // 3600
console.log(giftWrap.checked); // true
// outerHTML montre toujours l'attribut value écrit dans le HTML
console.log(quantity.outerHTML); // <input id="quantity" type="number" value="1">
"3" affecté va seulement dans la propriété value ; l'attribut value reste à 1. Les valeurs saisies se lisent dans la propriété value.C'est pareil pour checked : cocher la case n'ajoute pas d'attribut checked dans le HTML. Si tu vides le champ de quantité, value devient une chaîne vide, et Number("") renvoie 0 : une fois la valeur convertie en nombre, tu ne peux plus distinguer un champ vide d'un 0 saisi.
Recompter à chaque frappe — les événements input et change
Dans l'éditeur d'articles d'un blog d'entreprise, un compteur comme « 11 / 40 » s'affiche sous le champ du titre et doit se mettre à jour pendant la saisie. Si tu lis value une seule fois à l'ouverture de la page, les caractères tapés ensuite n'apparaissent jamais dans le compteur.
Un champ déclenche un événement input à chaque caractère tapé, et un événement change quand sa valeur a changé et que le focus quitte le champ. Comme tu ne peux pas taper dans la console, tu crées toi-même l'événement en indiquant son nom, comme dans new Event("input"), puis tu le déclenches sur le champ avec dispatchEvent, une méthode qui déclenche sur l'élément l'événement qu'on lui passe.
document.body.innerHTML = `<form id="post-form"><input id="post-title"><p id="title-count">0 / 40</p></form>`;
const postTitle = document.querySelector("#post-title");
const titleCount = document.querySelector("#title-count");
// input : recompter les caractères à chaque frappe
postTitle.addEventListener("input", () => {
titleCount.textContent = `${postTitle.value.length} / 40`;
});
// change : enregistrer un brouillon quand la valeur a changé et que le focus quitte le champ
postTitle.addEventListener("change", () => console.log("Brouillon enregistré"));
// Une simple affectation à value
postTitle.value = "Point hebdo";
console.log(titleCount.textContent); // 0 / 40
// Déclencher un événement appelle les écouteurs enregistrés sous le même nom
postTitle.dispatchEvent(new Event("input"));
console.log(titleCount.textContent); // 11 / 40
postTitle.dispatchEvent(new Event("change")); // Brouillon enregistré
L'écouteur input est appelé à chaque caractère : le travail qui n'a pas besoin de se faire à chaque frappe, comme l'enregistrement d'un brouillon, a sa place dans un écouteur change, déclenché quand l'utilisateur quitte le champ. Le tableau ci-dessous indique, pour chaque action de l'utilisateur, lequel des deux événements, input ou change, se déclenche.
| Action | Événement input | Événement change |
|---|---|---|
| Taper un caractère dans un champ | À chaque caractère | Non |
| Quitter le champ (clic ou Tab) | Non | Si la valeur a changé |
| Cliquer sur une case à cocher | Oui | Oui |
Annuler un envoi — submit et preventDefault
Un formulaire d'inscription à une newsletter doit afficher un message d'erreur et bloquer l'envoi quand l'adresse e-mail ne contient pas de @. Au clic sur le bouton d'envoi, le navigateur envoie le contenu du formulaire et charge une nouvelle page : même si ton écouteur place un message d'erreur sur la page, il disparaît avec elle.
Juste avant l'envoi d'un formulaire, un événement submit se déclenche sur l'élément form. Si tu appelles preventDefault (qui annule l'action par défaut du navigateur après l'événement) dans cet écouteur, le formulaire n'est pas envoyé. Pour savoir si l'annulation a pris effet, lis la propriété defaultPrevented de l'événement (elle vaut true si c'est le cas).
document.body.innerHTML = `<form id="newsletter-form"><input id="newsletter-email"><button>S'abonner</button><p id="email-error"></p></form>`;
const newsletterForm = document.querySelector("#newsletter-form");
const emailInput = document.querySelector("#newsletter-email");
const emailError = document.querySelector("#email-error");
// Appelé juste avant l'envoi. N'annule l'envoi que s'il n'y a pas de @
newsletterForm.addEventListener("submit", (event) => {
if (!emailInput.value.includes("@")) {
event.preventDefault();
emailError.textContent = "L'adresse ne contient pas de @";
}
});
// Au lieu d'un vrai envoi, on crée et on déclenche un événement submit annulable
emailInput.value = "lea.example.fr";
const submitEvent = new Event("submit", { cancelable: true });
newsletterForm.dispatchEvent(submitEvent);
console.log(submitEvent.defaultPrevented); // true (preventDefault a été appelé)
console.log(emailError.textContent); // L'adresse ne contient pas de @
Les colonnes de gauche et de droite ont la même saisie ; la seule différence, c'est l'appel ou non à preventDefault(). L'événement submit se déclenche sur l'élément form, pas sur le bouton d'envoi : enregistre donc l'écouteur sur le form. Le même écouteur reçoit aussi les envois faits en appuyant sur Entrée dans un champ.
click() sur un bouton d'envoi envoie vraiment le formulaire
Un button placé dans un form devient un bouton d'envoi si tu ne précises pas son type : appeler click() dessus sans annuler envoie réellement le formulaire : la page se recharge et tu ne vois aucun résultat. Un submit déclenché avec dispatchEvent n'envoie rien, mais sans { cancelable: true }, preventDefault() n'a aucun effet.
Rassembler toutes les valeurs des champs — FormData
Prends maintenant un formulaire d'adresse de livraison : tu veux regrouper dans un seul objet le nom du destinataire, le code postal et l'option de dépôt du colis devant la porte. Avec querySelector et value pour chaque champ, chaque nouveau champ t'oblige à ajouter une ligne pour le lire. Quand un formulaire est envoyé, chaque champ transmet sa valeur associée à son attribut name, le nom qui l'identifie.
new FormData(form) crée un FormData, un objet qui rassemble les paires nom–valeur des champs du formulaire à partir de leur attribut name ; get("zip") lit une valeur à partir de son nom. Passe-le ensuite à Object.fromEntries, qui fait l'inverse d'Object.entries en construisant un objet à partir d'une liste de paires [nom, valeur] : tu obtiens un seul objet qui contient tous les champs.
document.body.innerHTML = `<form id="shipping-form">
<input id="address-search" value="Shibuya">
<input name="recipient" value="Léa Martin">
<input name="zip" value="150-0001">
<input name="dropOff" type="checkbox">
<input name="notify" type="checkbox" checked>
</form>`;
const shippingForm = document.querySelector("#shipping-form");
// Rassembler les paires nom–valeur des champs du formulaire
const shippingData = new FormData(shippingForm);
console.log(shippingData.get("zip")); // 150-0001
console.log(shippingData.get("dropOff")); // null
// Construire un objet à partir des paires nom–valeur
const shipping = Object.fromEntries(shippingData);
console.log(JSON.stringify(shipping));
// {"recipient":"Léa Martin","zip":"150-0001","notify":"on"}
| Champ | Condition | Valeur dans FormData |
|---|---|---|
| Recherche d'adresse | Pas d'attribut name | Absent |
| recipient et zip | Avec name, champ texte | Léa Martin et 150-0001 |
| dropOff | Avec name, non cochée | Absent ; get renvoie null |
| notify | Avec name, cochée | on (sans attribut value) |
Comme une Map, un FormData permet de parcourir ses paires [nom, valeur] dans l'ordre : tu peux donc le passer directement à Object.fromEntries. Tant que tu donnes un attribut name aux nouveaux champs, tu n'as aucune ligne à ajouter pour les lire.
Afficher un FormData directement donne {}
La console des exercices convertit les objets en chaînes JSON avant de les afficher : si tu passes directement un FormData, comme dans console.log(shippingData), elle affiche {} alors que les valeurs des champs ont bien été rassemblées. Vérifie son contenu en le lisant avec get, ou en le transformant d'abord en objet avec Object.fromEntries.
Vérification des connaissances
Répondez à chaque question une par une.
Question 2Dans l'écouteur submit d'un formulaire, qu'annule l'appel à event.preventDefault() ?
Question 3Pour une case non cochée avec name="dropOff", que renvoie get("dropOff") sur le FormData ?