mitmario.dev

Eingaben prüfen und Fehler anzeigen

JavaScript Im Browser 4 Min Lesezeit 3 BeispieleLektion 3 von 6

Ein Formular, das jede Eingabe annimmt, ist bequem für den Entwickler und lästig für alle anderen. Prüfen heißt aber nicht nur „ablehnen”, sondern vor allem: verständlich sagen, was fehlt.

Erst einmal kann HTML das schon

Was HTML schon kann
const protokoll = document.getElementById("protokoll");

for (const feld of document.querySelectorAll("input")) {
  // checkValidity fragt, ob das Feld nach den HTML-Regeln
  // in Ordnung ist. validationMessage ist der Text, den der
  // Browser dazu anzeigen würde, in der Sprache des Nutzers.
  const zeile = [
    feld.name,
    feld.checkValidity() ? "gültig" : "ungültig",
    feld.validationMessage || "keine Meldung",
  ].join(" | ");
  protokoll.textContent = `${protokoll.textContent}${zeile}\n`;
}

// validity sagt sogar, WORAN es lag.
const alter = document.getElementById("alter");
console.log("zu klein?", alter.validity.rangeUnderflow);
console.log("leer?", document.getElementById("name").validity.valueMissing);
console.log("Muster verfehlt?", document.getElementById("plz").validity.patternMismatch);

// Und für das ganze Formular auf einmal.
console.log("Formular gültig?", document.getElementById("formular").checkValidity());

Bevor eine einzige Zeile JavaScript nötig ist, bringt HTML einiges mit: required, type="email", min und max, minlength und maxlength, pattern.

Das ist mehr wert, als es aussieht. Diese Regeln wirken ohne JavaScript, sie funktionieren auf jedem Gerät, und ihre Meldungen kommen in der Sprache des Benutzers, ohne dass jemand sie übersetzen muss.

Drück im ersten Beispiel einmal auf Absenden. Alle vier Felder sind absichtlich falsch gefüllt, und der Browser hält am ersten davon an und stellt seine eigene Blase daneben, auf Deutsch, ohne eine Zeile von dir. Abgeschickt wird nichts. Genau diese Blase schaltest du mit novalidate ab, und ab da bist du für die Meldung zuständig.

Aus JavaScript kommst du an dieselben Regeln heran:

feld.checkValidity() liefert true oder false. feld.validationMessage ist der Text, den der Browser anzeigen würde. feld.validity sagt, woran es lag: valueMissing, typeMismatch, rangeUnderflow, patternMismatch und einige mehr. formular.checkValidity() fragt für alle Felder auf einmal.

Aus der Eingabezeile der Console geht das genauso, im ersten Beispiel etwa alter.validity.rangeUnderflow: true, die 12 liegt unter 18. Setz dann alter.value = "30" und frag alter.checkValidity(): true. Die Regeln aus dem Markup prüfen den Wert, der jetzt im Feld steht, nicht den aus dem Attribut. alter.validity als Ganzes getippt antwortet nur mit ValidityState {}, die einzelnen Fragen musst du selbst stellen; das ist dieselbe Vererbung wie beim Event in Lektion 12.2.

Wirf das nicht weg, ergänze es. Wer eigene Meldungen anzeigt, setzt novalidate ans Formular und schaltet damit die eingebauten Meldungen ab, damit nicht zwei Meldungen zur selben Sache dastehen. Die Attribute bleiben trotzdem im Markup: Sie beschreiben, was das Feld verlangt, und andere Werkzeuge lesen das aus.

Die eigene Prüfung

Die eigene Prüfung
const protokoll = document.getElementById("protokoll");

// novalidate am Formular schaltet die eingebauten Meldungen
// ab. Man tut das genau dann, wenn man eigene anzeigt, sonst
// stehen zwei Meldungen zu derselben Sache da.

// Das Muster aus Abschnitt 9. Bewusst grob: eine Adresse
// wirklich zu prüfen geht nur, indem man ihr etwas schickt.
const EMAIL = /^[^\s@]+@[^\s@]+\.[a-z]{2,}$/i;

function pruefe(feld) {
  const wert = feld.value.trim();
  if (feld.id === "email") {
    return EMAIL.test(wert) ? "" : "Bitte gib eine gültige E-Mail an";
  }
  return Number(wert) >= 18 ? "" : "Bitte gib ein Alter ab 18 an";
}

document.getElementById("formular").addEventListener("submit", (event) => {
  event.preventDefault();
  for (const feld of document.querySelectorAll("input")) {
    protokoll.textContent = `${protokoll.textContent}${feld.name}: ${pruefe(feld) || "in Ordnung"}\n`;
  }
});

console.log("Schick ab, ändere die Werte, schick wieder ab.");

Eigene Prüfungen braucht man für alles, was HTML nicht kennt: Ein Passwort, das mit einem zweiten übereinstimmen soll. Ein Datum, das nach einem anderen liegen muss. Eine Postleitzahl, die zum gewählten Land passt.

Das Werkzeug dafür ist Abschnitt 9. Eine Funktion, die ein Feld bekommt und eine Meldung zurückgibt, oder einen leeren Text, wenn alles in Ordnung ist.

Warum eine Meldung und nicht true/false: Der Aufrufer braucht ohnehin einen Text. Gibt die Funktion ihn gleich mit zurück, steht die Formulierung genau an der Stelle, an der die Regel steht, und nicht in einer zweiten Tabelle daneben.

Das Muster für E-Mail-Adressen ist dabei bewusst grob. Eine Adresse wirklich zu prüfen geht nur auf eine Art: eine Nachricht hinschicken und sehen, ob sie ankommt. Jedes andere Muster lehnt entweder gültige Adressen ab oder lässt ungültige durch, meistens beides.

Wo die Meldung hingehört

Wo die Meldung hingehört
const feld = document.getElementById("email");
const ziel = document.getElementById("fehler-email");

function melde(meldung) {
  // Der Text steht neben dem Feld, nicht irgendwo oben.
  ziel.textContent = meldung;

  // Eine Klasse macht ihn sichtbar, das Aussehen bleibt im CSS.
  ziel.classList.toggle("feld-fehler", meldung !== "");

  // aria-invalid sagt einem Screenreader, dass dieses Feld
  // das Problem ist. Zusammen mit aria-describedby im Markup
  // wird der Text dann beim Fokus mit vorgelesen.
  feld.setAttribute("aria-invalid", meldung !== "" ? "true" : "false");
}

document.getElementById("formular").addEventListener("submit", (event) => {
  event.preventDefault();
  melde(feld.value.includes("@") ? "" : "Bitte gib eine gültige E-Mail an");
});

// Beim Tippen wird erst dann neu geprüft, wenn schon eine
// Meldung steht. Sonst meckert die Seite beim ersten Zeichen.
feld.addEventListener("input", () => {
  if (feld.getAttribute("aria-invalid") === "true") {
    melde(feld.value.includes("@") ? "" : "Bitte gib eine gültige E-Mail an");
  }
});

console.log("Schick leer ab und tipp dann eine Adresse.");

Neben das Feld. Nicht an den Kopf der Seite, wo man erst suchen muss, welches der acht Felder gemeint ist. Und auf keinen Fall in ein Hinweisfenster: das blockiert die Seite, verschwindet nach dem Wegklicken und lässt sich nicht noch einmal nachlesen.

Mit aria-describedby verbunden. Das Attribut steht am Feld und zeigt auf die id des Meldungsbereichs. Wer mit einem Screenreader in das Feld springt, bekommt die Meldung dann mit vorgelesen, statt sie zu übersehen. Es gehört ins Markup, auch solange die Meldung leer ist.

Mit aria-invalid am Feld. Es sagt: Dieses hier ist das Problem. Lektion 20.4 geht darauf noch einmal genauer ein.

Nicht nur über die Farbe. Ein roter Rahmen allein ist für einen Teil der Leser kein Unterschied. Es braucht den Text.

Wann geprüft wird

Beim Absenden immer. Das ist der Moment, in dem der Benutzer sagt, dass er fertig ist.

Beim Tippen nur, wenn schon eine Meldung steht. Eine Seite, die beim ersten Buchstaben einer Adresse „ungültig” ruft, ist unangenehm zu bedienen: Man ist ja noch mittendrin. Steht dagegen bereits eine Meldung da, will man sofort sehen, wenn sie behoben ist.

Im dritten Beispiel steht genau diese Bedingung: Beim input wird nur dann neu geprüft, wenn aria-invalid schon true ist.

Und der Satz, der am Ende zählt

Eine Prüfung im Browser ist Bequemlichkeit, keine Sicherheit.

Sie hilft dem Benutzer, seine Eingabe zu korrigieren, bevor er absendet. Sie hält niemanden auf, der es darauf anlegt: Der Code liegt offen, jeder kann ihn abschalten, und eine Anfrage lässt sich auch ganz ohne Seite verschicken.

Alles, was wirklich stimmen muss, wird auf dem Server noch einmal geprüft. Immer. Ohne Ausnahme. Was hier steht, ist die freundliche Hälfte davon.

Zum Mitnehmen

Eine Prüfung im Browser ist Bequemlichkeit, keine Sicherheit. Wer die Seite umgeht, umgeht sie mit. Der Server prüft ohnehin noch einmal.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

Was in dieser Lektion steckt

  • Artikel mit 3 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe im Editor, direkt im Browser geprüft

    Öffnet sich mit dem Basis Konto.