Files
dogfather-universe/assets/js/wd-anfrage.js
T
DogFatherGitandClaude Opus 5 eaeaec6b56 Anfrageformular fertig - vier Schritte, Zwischenspeicherung, echte Absendung
Masterplan S.10 "Projektanfrage ohne Informationsverlust" umgesetzt.
End-to-End gegen die echte Datenbank belegt: Anfrage A-2608-0002 liegt drin.
38/38 Browsertests, 45/45 Handy-Abnahme.

Aufbau: 4 Schritte + Zusammenfassung + Dankeseite.
- Zwischenspeicherung nach jedem Tastendruck. Geschlossener Tab, leerer
  Akku oder versehentliches Zurueck kosten keine Arbeit. Entwuerfe
  verfallen nach 30 Tagen -- ein halbes Jahr alter Entwurf verwirrt mehr
  als er hilft.
- Folgefragen erscheinen nur passend zur Auswahl und werden beim
  Zurueckwechseln NICHT mitgeschickt, sonst landen alte Shop-Antworten in
  einer Onepager-Anfrage.
- Die Zusammenfassung wird aus den Feldern gelesen, nicht aus einem
  nebenher gepflegten Objekt -- sie kann dadurch nie etwas anderes
  behaupten als das, was tatsaechlich abgeschickt wird.
- Telefon wird zur Pflicht, sobald "Per Telefon" gewaehlt ist. Ein Wunsch,
  der nicht erfuellbar ist, ist schlimmer als eine Pflichtangabe.

Drei Fehler, die erst der Test gefunden hat:
1. Der "Zurueck"-Knopf stand auch auf Schritt 1 da. Ursache: das
   hidden-Attribut wird nur ueber "display:none" umgesetzt und verliert
   gegen jedes eigene display -- und Knoepfe sind inline-flex. Zentral
   behoben ueber ".wd [hidden] { display:none !important }".
2. Nach erfolgreichem Absenden legte zeigeSeite() den geloeschten Entwurf
   sofort wieder an. Beim naechsten Besuch haette eine bereits gesendete
   Anfrage erneut im Formular gestanden.
3. Der Server meldet bei Spamverdacht bewusst Erfolg ohne Nummer. Ein
   echter Mensch, der zufaellig sehr schnell war, haette eine Dankeseite
   mit "—" gesehen und auf eine Antwort gewartet, die nie kommt. Jetzt
   ein ehrlicher Hinweis mit zweitem Weg, und der Entwurf bleibt erhalten.

Pruefskripte geschaerft:
- i18n-Pruefung liest jetzt auch assets/js/wd-<seite>.js, sonst meldet sie
  reihenweise "unbenutzt" fuer Schluessel, die aus dem Seitenskript kommen.
- Handy-Abnahme ueberspringt absichtlich unsichtbare Eingabefelder
  (Auswahlkacheln, Honigtopf). Fehlalarme sind das Ende jeder Pruefung,
  weil man sie irgendwann pauschal ignoriert.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-22 18:47:45 +02:00

541 lines
22 KiB
JavaScript

/* =====================================================================
wd-anfrage.js — Steuerung des mehrstufigen Anfrageformulars
(Masterplan S.10 "Projektanfrage ohne Informationsverlust")
Aufbau: 4 Eingabeschritte + Zusammenfassung + Dankeseite.
Leitgedanken:
- Nichts geht verloren. Jede Eingabe landet sofort im Browserspeicher;
ein geschlossener Tab, ein leerer Akku oder ein versehentliches
Zurück kostet keine Arbeit.
- Geprüft wird beim Weitergehen, nicht beim Tippen. Wer noch mitten im
Wort ist, soll nicht angemeckert werden.
- Die Zusammenfassung zeigt, was WIRKLICH abgeschickt wird — aus den
Feldern gelesen, nicht aus dem, was das Skript zu wissen glaubt.
===================================================================== */
(function () {
"use strict";
var SPEICHER = "wd-anfrage-entwurf";
var LETZTE_SEITE_EINGABE = 4; // danach kommt die Zusammenfassung
var SEITE_ZUSAMMENFASSUNG = 5;
var SEITE_DANKE = 6;
var form, seiten, zaehler, marken, meldung, knopfLeiste, btnZurueck, btnWeiter;
var aktuelleSeite = 1;
var geoeffnetAm = Date.now();
/* ------------------------------------------------------------------
Werte einsammeln. Bewusst direkt aus dem Formular statt aus einem
nebenher gepflegten Objekt: so kann die Anzeige nie etwas anderes
behaupten als das, was tatsächlich in den Feldern steht.
------------------------------------------------------------------ */
function werte() {
var d = new FormData(form);
var funktionen = d.getAll("funktionen");
/* Kategoriespezifische Felder tragen das Präfix "d_" und wandern
gesammelt in ein Unterobjekt. Neue Folgefragen brauchen dadurch
weder hier noch in der Datenbank eine Änderung. */
var details = {};
form.querySelectorAll("[name^='d_']").forEach(function (el) {
/* Nur Felder mitnehmen, die gerade sichtbar sind — sonst schickt
jemand, der von "Shop" auf "Onepager" zurückwechselt, immer noch
seine alten Shop-Antworten mit. */
if (el.closest("[hidden]")) return;
var wert = (el.value || "").trim();
if (wert) details[el.name.slice(2)] = wert;
});
var vorhanden = {};
form.querySelectorAll("[name^='v_']").forEach(function (el) {
if (el.checked) vorhanden[el.name.slice(2)] = el.value;
});
return {
paket: d.get("paket") || "",
ziel: (d.get("ziel") || "").trim(),
zielgruppe: (d.get("zielgruppe") || "").trim(),
seiten: (d.get("seiten") || "").trim(),
funktionen: funktionen,
details: details,
hatTexte: vorhanden.texte || "",
hatBilder: vorhanden.bilder || "",
hatLogo: vorhanden.logo || "",
hatDomain: vorhanden.domain || "",
budget: d.get("budget") || "",
termin: (d.get("termin") || "").trim(),
name: (d.get("name") || "").trim(),
email: (d.get("email") || "").trim(),
telefon: (d.get("telefon") || "").trim(),
kontaktweg: d.get("kontaktweg") || "email",
bemerkung: (d.get("bemerkung") || "").trim(),
datenschutz: !!d.get("datenschutz"),
website: (d.get("website") || "").trim(), // Honigtopf
};
}
/* ------------------------------------------------------------------
Zwischenspeicherung (Masterplan S.10: "Zwischenspeicherung verhindert
Datenverlust")
------------------------------------------------------------------ */
function speichern() {
try {
var w = werte();
delete w.website; // Honigtopf nie speichern
sessionStorageSchreiben(JSON.stringify({ stand: w, seite: aktuelleSeite, zeit: Date.now() }));
} catch (e) { /* Speicher voll oder gesperrt — kein Grund abzubrechen */ }
}
function sessionStorageSchreiben(text) {
/* localStorage, NICHT sessionStorage: der Entwurf soll auch morgen
noch da sein. Das ist der ganze Zweck. */
try { localStorage.setItem(SPEICHER, text); } catch (e) { /* egal */ }
}
function gespeichertesLesen() {
try {
var roh = localStorage.getItem(SPEICHER);
if (!roh) return null;
var daten = JSON.parse(roh);
/* Entwürfe verfallen nach 30 Tagen. Ein halbes Jahr alter Entwurf
verwirrt mehr, als er hilft. */
if (!daten.zeit || Date.now() - daten.zeit > 30 * 24 * 3600 * 1000) {
localStorage.removeItem(SPEICHER);
return null;
}
return daten;
} catch (e) { return null; }
}
function entwurfLoeschen() {
try { localStorage.removeItem(SPEICHER); } catch (e) { /* egal */ }
}
function wiederherstellen(daten) {
var w = daten.stand || {};
function setz(name, wert) {
var el = form.querySelector("[name='" + name + "']");
if (el && typeof wert === "string") el.value = wert;
}
setz("ziel", w.ziel); setz("zielgruppe", w.zielgruppe); setz("seiten", w.seiten);
setz("termin", w.termin); setz("name", w.name); setz("email", w.email);
setz("telefon", w.telefon); setz("bemerkung", w.bemerkung);
var budget = form.querySelector("[name='budget']");
if (budget && w.budget) budget.value = w.budget;
if (w.paket) {
var p = form.querySelector("[name='paket'][value='" + w.paket + "']");
if (p) p.checked = true;
}
if (w.kontaktweg) {
var k = form.querySelector("[name='kontaktweg'][value='" + w.kontaktweg + "']");
if (k) k.checked = true;
}
(w.funktionen || []).forEach(function (f) {
var el = form.querySelector("[name='funktionen'][value='" + f + "']");
if (el) el.checked = true;
});
Object.keys(w.details || {}).forEach(function (k) {
var el = form.querySelector("[name='d_" + k + "']");
if (el) el.value = w.details[k];
});
[["texte", w.hatTexte], ["bilder", w.hatBilder], ["logo", w.hatLogo], ["domain", w.hatDomain]]
.forEach(function (paar) {
if (!paar[1]) return;
var el = form.querySelector("[name='v_" + paar[0] + "'][value='" + paar[1] + "']");
if (el) el.checked = true;
});
if (w.datenschutz) {
var ds = form.querySelector("[name='datenschutz']");
if (ds) ds.checked = true;
}
}
/* ------------------------------------------------------------------
Tabelle "Was hast du schon?" — per JS gebaut, damit die vier Zeilen
nicht viermal fast gleich im HTML stehen.
------------------------------------------------------------------ */
function baueVorhandenTabelle() {
var koerper = document.getElementById("af-vorhanden");
if (!koerper) return;
var zeilen = [
["texte", "af3_texte"],
["bilder", "af3_bilder"],
["logo", "af3_logo"],
["domain", "af3_domain"],
];
var antworten = [["ja", "af3_ja"], ["teilweise", "af3_teilweise"], ["nein", "af3_nein"]];
koerper.innerHTML = zeilen.map(function (z) {
var knoepfe = antworten.map(function (a) {
return '<label class="wd-wahl-kachel" style="display:inline-block;margin:0 .3rem .3rem 0">' +
'<input type="radio" name="v_' + z[0] + '" value="' + a[0] + '" />' +
'<span class="wd-wahl-inhalt" style="padding:.55rem .8rem .55rem 2.4rem">' +
'<span class="wd-wahl-titel" style="font-size:.9rem" data-i18n="' + a[1] + '">' +
window.WD.t(a[1]) + "</span></span></label>";
}).join("");
return '<tr><td style="white-space:nowrap;vertical-align:middle"><strong data-i18n="' + z[1] + '">' +
window.WD.t(z[1]) + "</strong></td><td>" + knoepfe + "</td></tr>";
}).join("");
}
/* ------------------------------------------------------------------
Prüfung je Schritt
------------------------------------------------------------------ */
function fehlerSetzen(feldId, text) {
var feld = document.getElementById(feldId);
if (!feld) return;
feld.classList.toggle("wd-feld--fehler", !!text);
var p = feld.querySelector(".wd-feld-fehler");
if (p) p.textContent = text || "";
}
function fehlerLeeren() {
form.querySelectorAll(".wd-feld--fehler").forEach(function (f) {
f.classList.remove("wd-feld--fehler");
var p = f.querySelector(".wd-feld-fehler");
if (p) p.textContent = "";
});
}
/* Dieselbe absichtlich einfache Prüfung wie auf dem Server: Die einzige
zuverlässige Kontrolle ist ohnehin, ob die Antwortmail ankommt. Eine
strenge Regel lehnt vor allem gültige Adressen ab. */
function istEmail(s) {
var t = String(s || "").trim().toLowerCase();
var teile = t.split("@");
return teile.length === 2 && !!teile[0] && teile[1].indexOf(".") > 0 && !/\s/.test(t);
}
function pruefeSeite(nr) {
fehlerLeeren();
var w = werte();
var fehler = [];
if (nr === 1) {
if (!w.paket) { fehlerSetzen("feld-paket", window.WD.t("af_fehler_paket")); fehler.push("feld-paket"); }
if (!w.ziel) { fehlerSetzen("feld-ziel", window.WD.t("af_fehler_ziel")); fehler.push("feld-ziel"); }
}
if (nr === 4) {
if (!w.name) { fehlerSetzen("feld-name", window.WD.t("af_fehler_name")); fehler.push("feld-name"); }
if (!istEmail(w.email)) { fehlerSetzen("feld-email", window.WD.t("af_fehler_email")); fehler.push("feld-email"); }
/* Wer telefonisch kontaktiert werden möchte, muss eine Nummer
angeben — sonst kann der Wunsch gar nicht erfüllt werden. */
if (w.kontaktweg === "telefon" && !w.telefon) {
fehlerSetzen("feld-telefon", window.WD.t("af_fehler_telefon")); fehler.push("feld-telefon");
}
if (!w.datenschutz) {
fehlerSetzen("feld-datenschutz", window.WD.t("af_fehler_datenschutz")); fehler.push("feld-datenschutz");
}
}
if (fehler.length) {
window.WD.meldung(meldung, window.WD.t("af_fehler_schritt"), "fehler");
var erstes = document.getElementById(fehler[0]);
if (erstes) {
erstes.scrollIntoView({ behavior: "smooth", block: "center" });
var eingabe = erstes.querySelector("input, textarea, select");
if (eingabe) setTimeout(function () { eingabe.focus({ preventScroll: true }); }, 300);
}
return false;
}
window.WD.meldungAus(meldung);
return true;
}
/* ------------------------------------------------------------------
Navigation
------------------------------------------------------------------ */
function zeigeSeite(nr) {
aktuelleSeite = nr;
seiten.forEach(function (s) {
s.classList.toggle("af-sichtbar", Number(s.dataset.seite) === nr);
});
marken.forEach(function (m) {
var i = Number(m.dataset.marke);
m.classList.toggle("af-aktiv", i === nr);
m.classList.toggle("af-erledigt", i < nr);
});
var fortschritt = document.getElementById("af-fortschritt");
var hinweis = document.getElementById("af-speicherhinweis");
if (nr === SEITE_DANKE) {
knopfLeiste.hidden = true;
if (fortschritt) fortschritt.hidden = true;
if (hinweis) hinweis.hidden = true;
} else {
knopfLeiste.hidden = false;
if (fortschritt) fortschritt.hidden = false;
if (hinweis) hinweis.hidden = false;
btnZurueck.hidden = nr === 1;
btnWeiter.textContent =
nr === SEITE_ZUSAMMENFASSUNG ? window.WD.t("af_senden")
: nr === LETZTE_SEITE_EINGABE ? window.WD.t("af_pruefen")
: window.WD.t("af_weiter");
zaehler.textContent =
window.WD.t("af_schritt") + " " + Math.min(nr, 4) + " " + window.WD.t("af_von") + " 4";
}
/* Nach oben, aber nur bis zum Formular — ein Sprung ganz nach oben
zwingt sonst bei jedem Schritt zum Zurückscrollen. */
var rahmen = document.getElementById("af-rahmen");
if (rahmen) {
var y = rahmen.getBoundingClientRect().top + window.scrollY - 80;
window.scrollTo({ top: Math.max(0, y), behavior: "smooth" });
}
/* Auf der Dankeseite NICHT mehr speichern.
Sonst passiert Folgendes: absenden() loescht den Entwurf, ruft dann
zeigeSeite(DANKE) auf — und diese Zeile legt ihn sofort wieder an.
Beim naechsten Besuch stuenden die Daten einer bereits gesendeten
Anfrage wieder im Formular, samt "Wir haben deine letzte Eingabe
wiederhergestellt". Genau so im Selbsttest aufgefallen. */
if (nr !== SEITE_DANKE) speichern();
}
function folgefragenAktualisieren() {
var w = werte();
var shop = w.paket === "shop" || w.funktionen.indexOf("shop") >= 0;
var vw = w.paket === "verwaltung" || w.funktionen.indexOf("login") >= 0;
var eShop = document.getElementById("af-folge-shop");
var eVw = document.getElementById("af-folge-verwaltung");
if (eShop) eShop.hidden = !shop;
if (eVw) eVw.hidden = !vw;
}
/* ------------------------------------------------------------------
Zusammenfassung
------------------------------------------------------------------ */
function beschriftung(schluessel, roh) {
if (!roh) return null;
var t = window.WD.t(schluessel);
return t === schluessel ? roh : t;
}
function baueZusammenfassung() {
var w = werte();
var ziel = document.getElementById("af-zusammenfassung");
var sicher = window.WD.sicher;
var leer = window.WD.t("af_zus_leer");
function zeile(was, wert) {
var istLeer = !wert;
return '<div class="af-zus-zeile"><span class="af-zus-was">' + sicher(was) + "</span>" +
'<span class="af-zus-wert' + (istLeer ? " af-leer" : "") + '">' +
sicher(istLeer ? leer : wert) + "</span></div>";
}
function block(titel, schrittNr, zeilen) {
return '<div class="af-zus-block"><div class="af-zus-kopf"><h3>' + sicher(titel) + "</h3>" +
'<button type="button" data-zu="' + schrittNr + '">' + sicher(window.WD.t("af_zus_aendern")) + "</button>" +
"</div>" + zeilen.join("") + "</div>";
}
var paketName = {
onepager: "pk_onepager_h3", website: "pk_website_h3", shop: "pk_shop_h3",
verwaltung: "pk_verwaltung_h3", betreuung: "betreuung_h3", unklar: "af1_unklar",
}[w.paket];
var funktionsNamen = {
kontaktformular: "af2_f_kontakt", galerie: "af2_f_galerie", termin: "af2_f_termin",
shop: "af2_f_shop", login: "af2_f_login", blog: "af2_f_blog",
sprachen: "af2_f_sprachen", karte: "af2_f_karte", newsletter: "af2_f_newsletter", app: "af2_f_app",
};
var funktionenText = w.funktionen
.map(function (f) { return beschriftung(funktionsNamen[f], f); })
.filter(Boolean)
/* HTML-Entities aus dem Wörterbuch (&amp;) in echte Zeichen —
hier ist reiner Text gefragt, er wird gleich escaped. */
.map(function (s) { return s.replace(/&amp;/g, "&"); })
.join(", ");
var antwortNamen = { ja: "af3_ja", teilweise: "af3_teilweise", nein: "af3_nein" };
var budgetNamen = {
"bis-500": "af3_budget_1", "500-1000": "af3_budget_2",
"1000-2500": "af3_budget_3", "ueber-2500": "af3_budget_4", unklar: "af3_budget_5",
};
var html = "";
html += block(window.WD.t("af_s1"), 1, [
zeile(window.WD.t("af1_paket_label"), beschriftung(paketName, w.paket)),
zeile(window.WD.t("af1_ziel_label"), w.ziel),
zeile(window.WD.t("af1_zielgruppe_label"), w.zielgruppe),
]);
var zeilen2 = [
zeile(window.WD.t("af2_seiten_label"), w.seiten),
zeile(window.WD.t("af2_funktionen_label"), funktionenText),
];
Object.keys(w.details).forEach(function (k) {
zeilen2.push(zeile(k.replace(/_/g, " "), w.details[k]));
});
html += block(window.WD.t("af_s2"), 2, zeilen2);
html += block(window.WD.t("af_s3"), 3, [
zeile(window.WD.t("af3_texte"), beschriftung(antwortNamen[w.hatTexte], w.hatTexte)),
zeile(window.WD.t("af3_bilder"), beschriftung(antwortNamen[w.hatBilder], w.hatBilder)),
zeile(window.WD.t("af3_logo"), beschriftung(antwortNamen[w.hatLogo], w.hatLogo)),
zeile(window.WD.t("af3_domain"), beschriftung(antwortNamen[w.hatDomain], w.hatDomain)),
zeile(window.WD.t("af3_budget_label"), beschriftung(budgetNamen[w.budget], w.budget)),
zeile(window.WD.t("af3_termin_label"), w.termin),
]);
html += block(window.WD.t("af_s4"), 4, [
zeile(window.WD.t("af4_name"), w.name),
zeile(window.WD.t("af4_email"), w.email),
zeile(window.WD.t("af4_telefon"), w.telefon),
zeile(window.WD.t("af4_weg"), window.WD.t(w.kontaktweg === "telefon" ? "af4_weg_telefon" : "af4_weg_email")),
zeile(window.WD.t("af4_bemerkung"), w.bemerkung),
]);
ziel.innerHTML = html;
ziel.querySelectorAll("[data-zu]").forEach(function (b) {
b.addEventListener("click", function () { zeigeSeite(Number(b.dataset.zu)); });
});
}
/* ------------------------------------------------------------------
Absenden
------------------------------------------------------------------ */
async function absenden() {
var w = werte();
window.WD.knopfSperren(btnWeiter, true, window.WD.t("af_sendet"));
window.WD.meldungAus(meldung);
try {
var antwort = await window.WD.api("/webdesign/anfrage", {
method: "POST",
body: Object.assign({}, w, {
sprache: window.WD.getSprache(),
geoeffnetAm: geoeffnetAm,
}),
});
/* Erst NACH bestätigtem Eingang löschen. Bricht das Absenden ab,
sind die Eingaben noch da und man kann es erneut versuchen. */
/* Der Server meldet bei verdächtigen Absendungen bewusst Erfolg,
speichert aber nichts und liefert KEINE Nummer (Honigtopf oder
verdächtig kurze Ausfüllzeit — siehe routes/webdesign-anfragen.js).
Das ist gegenüber automatischen Skripten richtig so.
Für einen echten Menschen, der zufällig sehr schnell war, wäre
eine Dankeseite mit "—" als Nummer aber eine stille Lüge: er
glaubt, die Anfrage sei unterwegs, und wartet auf eine Antwort,
die nie kommt. Deshalb hier ohne Nummer ein ehrlicher Hinweis mit
einem zweiten Weg. */
var nummerFeld = document.getElementById("af-nummer");
if (antwort.nummer) {
entwurfLoeschen();
nummerFeld.textContent = antwort.nummer;
} else {
/* Entwurf ABSICHTLICH behalten — falls wirklich nichts ankam,
soll die Arbeit nicht weg sein. */
nummerFeld.textContent = "—";
nummerFeld.style.fontSize = "1rem";
nummerFeld.style.letterSpacing = "normal";
nummerFeld.textContent = window.WD.t("af_danke_ohne_nummer");
}
zeigeSeite(SEITE_DANKE);
} catch (fehler) {
/* Feldbezogene Fehler vom Server anzeigen — der Server ist die
maßgebliche Prüfung, nicht der Browser. */
var felder = fehler && fehler.daten && fehler.daten.felder;
if (felder) {
Object.keys(felder).forEach(function (f) { fehlerSetzen("feld-" + f, felder[f]); });
}
window.WD.meldung(meldung, fehler.message || window.WD.t("fehler_allg"), "fehler");
window.WD.knopfSperren(btnWeiter, false);
}
}
/* ------------------------------------------------------------------
Start
------------------------------------------------------------------ */
function start() {
form = document.getElementById("af-form");
if (!form) return;
seiten = Array.prototype.slice.call(form.querySelectorAll(".af-seite"));
marken = Array.prototype.slice.call(document.querySelectorAll(".af-schritt-marke"));
zaehler = document.getElementById("af-zaehler");
meldung = document.getElementById("af-meldung");
knopfLeiste = document.getElementById("af-knopfleiste");
btnZurueck = document.getElementById("af-zurueck");
btnWeiter = document.getElementById("af-weiter");
baueVorhandenTabelle();
/* Paket aus der Adresszeile vorauswählen — die Leistungsseite
verlinkt mit ?paket=shop. Spart einen Klick und zeigt, dass der
Weg mitgedacht ist. */
var params = new URLSearchParams(location.search);
var paket = params.get("paket");
if (paket) {
var el = form.querySelector("[name='paket'][value='" + paket.replace(/[^a-z]/g, "") + "']");
if (el) el.checked = true;
}
var entwurf = gespeichertesLesen();
if (entwurf) {
wiederherstellen(entwurf);
var box = document.getElementById("af-wieder");
if (box) box.hidden = false;
}
document.getElementById("af-verwerfen").addEventListener("click", function () {
entwurfLoeschen();
location.reload();
});
folgefragenAktualisieren();
btnWeiter.addEventListener("click", function () {
if (aktuelleSeite === SEITE_ZUSAMMENFASSUNG) return absenden();
if (!pruefeSeite(aktuelleSeite)) return;
if (aktuelleSeite === LETZTE_SEITE_EINGABE) {
baueZusammenfassung();
zeigeSeite(SEITE_ZUSAMMENFASSUNG);
return;
}
zeigeSeite(aktuelleSeite + 1);
});
btnZurueck.addEventListener("click", function () {
if (aktuelleSeite > 1) zeigeSeite(aktuelleSeite - 1);
});
/* Eingaben laufend sichern und Folgefragen nachziehen. "input" statt
"change", damit auch beim Tippen gespeichert wird. */
form.addEventListener("input", function () { folgefragenAktualisieren(); speichern(); });
form.addEventListener("change", function () { folgefragenAktualisieren(); speichern(); });
/* Enter im Formular soll weitergehen, nicht absenden — sonst ist die
Anfrage nach dem ersten Feld unterwegs. */
form.addEventListener("submit", function (e) { e.preventDefault(); btnWeiter.click(); });
/* Telefon wird bei "Per Telefon" zur Pflicht — das Wörtchen
"optional" muss dann verschwinden, sonst ist die spätere
Fehlermeldung unfair. */
form.querySelectorAll("[name='kontaktweg']").forEach(function (r) {
r.addEventListener("change", function () {
var opt = document.getElementById("af-telefon-opt");
if (opt) opt.hidden = r.value === "telefon" && r.checked;
});
});
/* Nach einem Sprachwechsel Knopfbeschriftung, Zähler und die per JS
gebauten Teile neu setzen. */
document.addEventListener("wd-sprache-geaendert", function () {
baueVorhandenTabelle();
wiederherstellen({ stand: werte() }); // Auswahl der Tabelle retten
if (aktuelleSeite === SEITE_ZUSAMMENFASSUNG) baueZusammenfassung();
zeigeSeite(aktuelleSeite);
});
zeigeSeite(entwurf && entwurf.seite && entwurf.seite <= LETZTE_SEITE_EINGABE ? entwurf.seite : 1);
}
if (window.WD) start();
else document.addEventListener("wd-bereit", start);
})();