Abschnitt 9 · Lektion 5
Synthese: die Eingabeprüfung
Jetzt kommt alles aus diesem Abschnitt an einer Stelle zusammen, an der man es wirklich braucht: einer Eingabeprüfung. In Abschnitt 13 hängt genau diese Prüfung an einem echten Formular.
Der erste Anlauf
<h1>Ausgabe</h1>
<p>Was dieses Beispiel tut, steht in der Console.</p> // So schreibt man es beim ersten Mal.
function pruefeAlt(daten) {
if (!daten.name) return "Name fehlt";
if (!/^[A-Za-zÄÖÜäöüß]+([ -][A-Za-zÄÖÜäöüß]+)*$/.test(daten.name)) return "Name sieht komisch aus";
if (!daten.email) return "E-Mail fehlt";
if (!/^[^@\s]+@[^@\s]+\.[a-z]{2,}$/i.test(daten.email)) return "E-Mail stimmt nicht";
return null;
}
console.log(pruefeAlt({}));
console.log(pruefeAlt({ name: "Ida" }));
console.log(pruefeAlt({ name: "Ida", email: "keine" }));
console.log(pruefeAlt({ name: "Ida", email: "i@x.de" }));
// Es funktioniert. Nur: jede neue Regel ist eine neue Zeile
// Logik, die Meldungen stehen zwischen den Bedingungen, und
// die Liste der Regeln steht nirgends als Ganzes. So schreibt man es beim ersten Mal, und es ist nicht falsch: eine Kette aus if, jedes mit einer Bedingung und einem return mit der Meldung.
Wichtig ist zuerst die Entscheidung, überhaupt eine Meldung zurückzugeben und nicht nur false. Eine Prüfung, die „nein” sagt und nicht „warum”, ist für den Menschen davor wertlos. Er sieht ein rotes Feld und muss raten.
Der null-Fall am Ende heißt „alles in Ordnung”. Das ist die Umkehrung, an die man sich gewöhnen muss: kein Fehler ist der Erfolgsfall, und null heißt hier „nichts zu melden”.
Was an der Kette stört, merkt man erst bei der fünften Regel. Jede neue Regel ist eine neue Zeile Logik, die Meldungen stehen zwischen den Bedingungen verstreut, und nirgends steht die Liste der Regeln als Ganzes da.
Regeln sind Daten
<h1>Ausgabe</h1>
<p>Was dieses Beispiel tut, steht in der Console.</p> // Dieselbe Prüfung, aber die Regeln sind jetzt Daten.
const regeln = [
{ feld: "name", muster: /^[A-Za-zÄÖÜäöüß]+([ -][A-Za-zÄÖÜäöüß]+)*$/, meldung: "Name fehlt oder sieht komisch aus" },
{ feld: "email", muster: /^[^@\s]+@[^@\s]+\.[a-z]{2,}$/i, meldung: "E-Mail sieht nicht richtig aus" },
{ feld: "plz", muster: /^\d{5}$/, meldung: "Postleitzahl braucht fünf Ziffern" },
];
function pruefe(daten) {
for (const regel of regeln) {
const wert = daten[regel.feld] ?? "";
if (!regel.muster.test(wert)) return regel.meldung;
}
return null;
}
console.log(pruefe({}));
console.log(pruefe({ name: "Ida" }));
console.log(pruefe({ name: "Ida", email: "i@x.de" }));
console.log(pruefe({ name: "Ida", email: "i@x.de", plz: "04103" }));
// Eine neue Regel ist jetzt eine Zeile in der Liste,
// und die Schleife bleibt, wie sie ist. Der Umbau ist klein und ändert alles: die Regeln kommen in ein Array aus Objekten, jedes mit Feldname, Muster und Meldung. Die Prüfung selbst wird eine Schleife darüber.
Damit ist die Form aus Lektion 7.8 wieder da, und mit ihr alle Vorteile. Eine neue Regel ist eine Zeile in der Liste. Die Logik bleibt, wie sie ist. Und die Liste liest sich wie eine Tabelle: man sieht auf einen Blick, was geprüft wird.
Das ?? "" in der Schleife erledigt nebenbei den Fall „Feld fehlt ganz”. Ein fehlendes Feld ist undefined, und undefined in ein Muster zu stecken wäre eine stille Umwandlung nach Lektion 2.7. Mit dem leeren Text daraus schlägt jedes Muster fehl, das mindestens ein Zeichen verlangt, und die Meldung stimmt.
Genau das ist der Grund, warum die Muster mit + und nicht mit * arbeiten. Ein * würde den leeren Text durchlassen, und dann käme ein Formular ohne Eingaben als gültig durch.
Das Namensmuster ist dabei genau das aus Lektion 9.2, um ein Zeichen erweitert: In [ -] steht neben dem Bindestrich auch ein Leerzeichen, denn ein Name darf aus zwei Wörtern bestehen. Am Rand ist weiterhin keins von beiden erlaubt. Anna Lena und Jörg-Uwe kommen also durch, Ida und Ida- nicht.
Und weil es Daten sind, geht mehr
<h1>Ausgabe</h1>
<p>Was dieses Beispiel tut, steht in der Console.</p> const regeln = [
{ feld: "name", muster: /^[A-Za-zÄÖÜäöüß]+([ -][A-Za-zÄÖÜäöüß]+)*$/, meldung: "Name fehlt" },
{ feld: "email", muster: /^[^@\s]+@[^@\s]+\.[a-z]{2,}$/i, meldung: "E-Mail stimmt nicht" },
{ feld: "plz", muster: /^\d{5}$/, meldung: "Postleitzahl stimmt nicht" },
];
// Weil die Regeln eine Liste sind, ist alles aus Abschnitt 6
// darauf anwendbar. Hier: alle verletzten statt der ersten.
function alleFehler(daten) {
return regeln
.filter((regel) => !regel.muster.test(daten[regel.feld] ?? ""))
.map((regel) => regel.meldung);
}
console.log(alleFehler({}));
console.log(alleFehler({ name: "Ida", plz: "04103" }));
console.log(alleFehler({ name: "Ida", email: "i@x.de", plz: "04103" }));
// Und die Meldungen je Feld, für die Anzeige am Formular.
function fehlerJeFeld(daten) {
return Object.fromEntries(
regeln
.filter((regel) => !regel.muster.test(daten[regel.feld] ?? ""))
.map((regel) => [regel.feld, regel.meldung])
);
}
console.log(fehlerJeFeld({ name: "Ida" })); Sobald die Regeln eine Liste sind, ist Abschnitt 6 darauf anwendbar, und das kostet nichts extra.
filter und map liefern alle verletzten Regeln statt nur der ersten. Für ein Formular ist das meistens die bessere Rückmeldung: der Nutzer korrigiert alles auf einmal, statt nach jedem Absenden einen neuen Fehler zu sehen.
Mit Object.fromEntries aus Lektion 7.7 wird daraus eine Zuordnung von Feld zu Meldung. Genau diese Form braucht Abschnitt 13, um die Meldung an das richtige Eingabefeld zu schreiben.
Ob du die erste oder alle Meldungen zurückgibst, ist eine Entscheidung und keine Frage von richtig und falsch. Der Punkt ist, dass sie dich jetzt nur eine Zeile kostet.
Was Muster nicht können
<h1>Ausgabe</h1>
<p>Was dieses Beispiel tut, steht in der Console.</p> // Das grobe E-Mail-Muster: irgendetwas, ein @, irgendetwas,
// ein Punkt, mindestens zwei Buchstaben.
const grob = /^[^@\s]+@[^@\s]+\.[a-z]{2,}$/i;
console.log("normal:", grob.test("ida@example.com"));
console.log("mit Plus:", grob.test("ida+kurs@example.com"));
console.log("ohne Punkt:", grob.test("ida@example"));
console.log("zwei @:", grob.test("a@b@c.de"));
// Und hier ist die ehrliche Grenze: das hier ist gültig
// und wird abgelehnt.
console.log("gültig, abgelehnt:", grob.test("ida@localhost"));
// Verschachteltes gehört nicht in ein Muster. Das hier sieht
// aus wie es funktioniert und tut es nicht.
const tag = /<b>(.*?)<\/b>/;
console.log("einfach:", "<b>fett</b>".match(tag)[1]);
console.log("verschachtelt:", "<b>a<b>b</b>c</b>".match(tag)[1]); Zum Schluss zwei ehrliche Einschränkungen.
Die erste betrifft E-Mail-Adressen. Das vollständig korrekte Muster nach der zugehörigen Norm ist mehrere hundert Zeichen lang, und selbst dann prüft es nur die Form, nicht ob es die Adresse gibt. In der Praxis prüft man grob: irgendetwas, ein @, irgendetwas, ein Punkt, ein paar Buchstaben. Und dann schickt man eine Bestätigungsmail, denn nur die beantwortet die Frage, die einen wirklich interessiert.
Das Beispiel zeigt, wo das grobe Muster danebenliegt: ida@localhost ist technisch gültig und wird abgelehnt. Das ist ein bewusst in Kauf genommener Fehler, kein Versehen.
Die zweite betrifft verschachtelte Strukturen. Ein Muster kann nicht zählen, also kann es nicht wissen, welches schließende Tag zu welchem öffnenden gehört. Für HTML, JSON oder Klammerausdrücke sind reguläre Ausdrücke deshalb das falsche Werkzeug, und zwar grundsätzlich und nicht nur in schwierigen Fällen. Für HTML gibt es ab Abschnitt 10 etwas Besseres: das DOM.
Zum Mitnehmen
Eine Prüfung, die nur wahr oder falsch sagt, hilft dem Nutzer nicht. Sie muss sagen, was fehlt, und dafür gehören Muster und Meldung zusammen in eine Liste.
Jetzt du
Basis Konto, kostenlosZu 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 4 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe im Editor, direkt im Browser geprüft
Öffnet sich mit dem Basis Konto.