Abschnitt 13 · Lektion 2
Das submit-Ereignis
Formulare sind der häufigste Ort, an dem eine Seite Daten bekommt. Und sie haben eine eingebaute Reaktion, die man fast immer übernehmen will.
Am Formular, nicht am Knopf
<form id="richtig">
<input name="a" value="Tipp hier und drück die Eingabetaste">
<button type="submit">Absenden</button>
</form>
<form id="falsch">
<input name="b" value="Hier auch">
<button id="knopf" type="submit">Absenden</button>
</form>
<pre id="protokoll"></pre> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
button,
input,
select,
textarea {
font: inherit;
}
button {
padding: 0.4rem 0.8rem;
border: 1px solid #d6d3d1;
border-radius: 6px;
background: #fafaf9;
cursor: pointer;
}
input,
select,
textarea {
padding: 0.35rem 0.5rem;
border: 1px solid #d6d3d1;
border-radius: 6px;
} const protokoll = document.getElementById("protokoll");
function notiere(text) {
protokoll.textContent = `${protokoll.textContent}${text}\n`;
}
// So: der Listener hängt am Formular. Er bekommt jeden Weg mit,
// den Klick auf den Knopf genauso wie die Eingabetaste im Feld.
document.getElementById("richtig").addEventListener("submit", (event) => {
event.preventDefault();
notiere("richtig: abgefangen");
});
// So nicht: der Listener hängt am Knopf. Die Eingabetaste im
// Feld löst zwar ab, aber nicht diesen Handler.
document.getElementById("knopf").addEventListener("click", (event) => {
event.preventDefault();
notiere("falsch: nur der Klick wurde bemerkt");
});
// Damit die Seite stehen bleibt, fangen wir das zweite
// Formular zusätzlich sauber ab.
document.getElementById("falsch").addEventListener("submit", (event) => {
event.preventDefault();
notiere("falsch: abgeschickt, ohne dass der Knopf es merkte");
});
console.log("Drück in beiden Formularen die Eingabetaste im Feld."); Das Ereignis heißt submit und feuert am <form>.
Der Reflex, es an den Absendeknopf zu hängen, ist verständlich und falsch. Ein Formular lässt sich nämlich auf mehreren Wegen abschicken:
- ein Klick auf den Absendeknopf,
- die Eingabetaste in einem Textfeld,
- ein
form.requestSubmit()aus dem Code.
Ein Listener am Knopf bekommt nur den ersten mit. Die Eingabetaste im Feld schickt trotzdem ab, und dann passiert genau das, was dein Handler verhindern sollte.
Der Listener am Formular bekommt alle drei. Es gibt keinen Fall, in dem der Knopf die bessere Stelle wäre.
preventDefault ist hier Pflicht
Ohne event.preventDefault() schickt der Browser das Formular ab und lädt die Seite neu. Dein Handler läuft davor noch vollständig durch, deshalb sieht es einen Moment lang so aus, als hätte alles geklappt.
Das ist der Fall aus Lektion 12.4, und hier ist er der Normalfall statt der Ausnahme. Jeder submit-Handler in diesem Kurs beginnt mit preventDefault.
Sieh es dir einmal an, statt es zu glauben: Nimm die Zeile im Beispiel weg und schick das Formular ab. Im Reiter Netzwerk steht danach die Anfrage, die der Browser dafür zusammengebaut hat, samt deinen Feldern als Paare in der Adresse. Und die Seite steht wieder auf Anfang, denn sie wurde neu geladen. Genau das ist der Grund für die Zeile.
Das eingebaute Absenden ist übrigens nichts Schlechtes: Ein Formular, das ohne JavaScript an den Server geht, funktioniert auch dann noch, wenn das Skript ausfällt. Wer es abfängt, übernimmt damit die Verantwortung für alles, was danach kommt, und Abschnitt 16 zeigt, wie das Verschicken dann aussieht.
Der Standard-type eines button
<form id="formular">
<input name="a" value="Ida">
<button id="ohne">ohne type-Angabe</button>
<button id="mit" type="button">type="button"</button>
<button id="absenden" type="submit">type="submit"</button>
</form>
<pre id="protokoll"></pre> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
button,
input,
select,
textarea {
font: inherit;
}
button {
padding: 0.4rem 0.8rem;
border: 1px solid #d6d3d1;
border-radius: 6px;
background: #fafaf9;
cursor: pointer;
}
input,
select,
textarea {
padding: 0.35rem 0.5rem;
border: 1px solid #d6d3d1;
border-radius: 6px;
} const protokoll = document.getElementById("protokoll");
function notiere(text) {
protokoll.textContent = `${protokoll.textContent}${text}\n`;
}
// Ein button ohne type-Angabe ist ein Absende-Knopf. Das
// überrascht regelmäßig, besonders bei einem Knopf, der
// eigentlich nur etwas ein- und ausklappen soll.
console.log("ohne Angabe:", document.getElementById("ohne").type);
console.log("mit Angabe:", document.getElementById("mit").type);
console.log("ausdrücklich:", document.getElementById("absenden").type);
document.getElementById("formular").addEventListener("submit", (event) => {
event.preventDefault();
notiere("abgeschickt");
});
document.getElementById("mit").addEventListener("click", () => {
notiere("nur geklickt, kein Absenden");
});
console.log("Drück alle drei Knöpfe."); Ein <button> ohne type-Angabe ist ein Absende-Knopf. Der Standard ist submit, nicht button.
Das überrascht zuverlässig, und zwar an einer unangenehmen Stelle: Ein Knopf mitten im Formular, der eigentlich nur einen Bereich aufklappen soll, schickt beim Klick das ganze Formular ab und lädt die Seite neu.
Die Regel: Jeder Knopf innerhalb eines Formulars, der nicht absenden soll, bekommt ausdrücklich type="button". In diesem Kurs steht das überall dran, auch außerhalb von Formularen, damit die Gewohnheit sitzt.
Es gibt noch type="reset", das das Formular zurücksetzt. Es kommt hier nicht vor: Ein Knopf, der versehentlich alle Eingaben löscht, ist selten das, was jemand wollte.
Aufräumen nach dem Absenden
<form id="formular">
<input id="frei" name="frei" value="">
<input id="vorbelegt" name="vorbelegt" value="Steht im Markup">
<button type="submit">Absenden</button>
</form>
<p id="ausgabe">noch nichts</p> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
p {
margin: 0 0 0.75rem;
}
button,
input,
select,
textarea {
font: inherit;
}
button {
padding: 0.4rem 0.8rem;
border: 1px solid #d6d3d1;
border-radius: 6px;
background: #fafaf9;
cursor: pointer;
}
input,
select,
textarea {
padding: 0.35rem 0.5rem;
border: 1px solid #d6d3d1;
border-radius: 6px;
} const formular = document.getElementById("formular");
formular.addEventListener("submit", (event) => {
event.preventDefault();
const wert = document.getElementById("frei").value;
document.getElementById("ausgabe").textContent = `Bekommen: "${wert}"`;
// reset setzt auf den Stand aus dem MARKUP zurück, nicht
// auf leer. Das zweite Feld steht danach wieder auf seinem
// Anfangswert.
formular.reset();
console.log("frei:", JSON.stringify(document.getElementById("frei").value));
console.log("vorbelegt:", JSON.stringify(document.getElementById("vorbelegt").value));
});
console.log("Tipp in beide Felder und schick ab."); formular.reset() setzt alle Felder zurück, und zwar auf den Stand aus dem Markup, nicht auf leer. Ein Feld mit value="Steht im Markup" steht danach wieder genau darauf.
Das ist meistens das Gewünschte, und es ist ein Unterschied, den man kennen muss: Wer wirklich leeren will, setzt die Werte selbst.
Ob überhaupt zurückgesetzt wird, ist eine Frage des Zwecks. Eine Eingabe, die man einmal macht, darf verschwinden. Ein Filter, den man nachjustiert, sollte stehen bleiben. Und wenn etwas schiefgeht, gehört der Inhalt auf keinen Fall gelöscht: Nichts ärgert mehr, als ein Formular nach einer Fehlermeldung noch einmal auszufüllen.
Zum Mitnehmen
submit hängt am form, nicht am Knopf. Die Eingabetaste im Feld schickt genauso ab, und ein Listener am Knopf verpasst genau das.
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 3 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe im Editor, direkt im Browser geprüft
Öffnet sich mit dem Basis Konto.