Apprenez en lisant dans l'ordre

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">
Les deux value de l'input quantity
Attribut valuevalue="1"écrit dans le HTMLReste à 1après la saisieouterHTMLmontre value="1"Propriété valueSaisie actuelle"3"Change à chaquefrappeSe lit avecquantity.value
Le "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.

Pour une réservation de voiture de location, calcule le prix à partir du nombre de jours et du choix d'un siège enfant. Le champ du nombre de jours rentalDays, qui a déjà reçu une valeur, et la case à cocher childSeat sont déjà déclarés.

① Affiche le type de la valeur du champ du nombre de jours.

② Convertis le nombre de jours en nombre et affiche le prix à 6000 yens par jour.

③ Si la case est cochée, ajoute 500 yens par jour et affiche le total pour toute la location.

(Si tout s'exécute correctement, une explication apparaîtra.)

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

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é
Une affectation seule ne met pas à jour le compteur
postTitle.value= "Point hebdo"Aucun événementinputL'écouteur n'estpas appeléReste à0 / 40dispatchEvent(…)après affectationUn événementinput se produitL'écouteur litvalue.lengthPasse à11 / 40
Affecter une valeur à value ne déclenche pas d'événement input : le compteur reste à 0 / 40. Déclencher l'événement avec dispatchEvent appelle l'écouteur.

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èreNon
Quitter le champ (clic ou Tab)NonSi la valeur a changé
Cliquer sur une case à cocherOuiOui

Sur l'écran de paiement, affiche les champs d'adresse seulement quand « Livrer à une autre adresse » est coché. La case à cocher otherAddress et le bloc des champs d'adresse addressFields sont déjà déclarés.

① Enregistre un écouteur qui retire la classe hidden du bloc d'adresse quand la case est cochée.

② Avant de déclencher un clic, affiche le HTML du bloc d'adresse.

③ Déclenche un clic sur la case, puis affiche si elle est cochée et le HTML du bloc d'adresse.

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

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 @
Ce que changent le @ et preventDefault
L'écouteur submitest appeléSans @ :lea.example.frAvec @ :lea@example.frSans @ :lea.example.frpreventDefault()et un messagepreventDefault()n'est pas appelépreventDefault()a été oubliéPas envoyé ;le message resteLe navigateurl'envoieEnvoyé ; lemessage disparaît
Appeler preventDefault() quand il n'y a pas de @ garde le message d'erreur sur la page. Si tu oublies de l'appeler, le navigateur change de page et le message d'erreur disparaît avec elle.

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.

Vérifie qu'un nouveau mot de passe est assez long. passwordForm, newPassword et passwordError sont déjà déclarés.

① Enregistre un écouteur qui annule l'envoi et place un message d'erreur si le mot de passe fait moins de huit caractères, et qui efface le message sinon.

② Avec « cat2026 » dans le champ, déclenche un événement submit annulable et affiche s'il a été annulé.

③ Avec « cat2026! » dans le champ, crée un nouvel événement, déclenche-le, puis affiche s'il a été annulé et le HTML de la zone d'erreur.

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

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"}
ChampConditionValeur dans FormData
Recherche d'adressePas d'attribut nameAbsent
recipient et zipAvec name, champ texteLéa Martin et 150-0001
dropOffAvec name, non cochéeAbsent ; get renvoie null
notifyAvec name, cochéeon (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.

Sur le formulaire de candidature d'un site d'emploi, vérifie ce qui a été saisi avant l'envoi. Le formulaire applyForm est déjà déclaré.

① Dans un écouteur submit, annule l'envoi, regroupe les valeurs des champs dans un objet et affiche-le sous forme de chaîne JSON.

② Dans le même écouteur, lis la valeur de la case d'accord pour recevoir des e-mails, et affiche « Pas de consentement » s'il n'y a pas de valeur.

③ Déclenche un événement submit annulable.

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat
QUIZ

Vérification des connaissances

Répondez à chaque question une par une.

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 ?

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 ?