Eine Eingabe geht nicht mehr wortlos verloren
Filipe: "Was passiert, wenn jemand eine Funktion abbricht?" GEMESSEN: Acht Dialoge im Workspace enthalten Eingabefelder -- "Aufgabe bearbeiten" (10 Felder), der Tageseintrag in der Leistung (8), Ziele, Netzwerk, Import, ein neuer Chat-Raum, ein Creator. Bei allen galt: Esc, Klick daneben oder "Abbrechen" wirft ALLES weg, wortlos. Wer zehn Felder ausgefuellt hat und mit dem Daumen den Rand trifft, faengt von vorn an. Neu: window.verwurfWache() in nachfrage.js. BEIDE SCHLIESSWEGE, denn sie laufen verschieden: Esc / Klick daneben loest `cancel` aus, close() wird NICHT gerufen "Abbrechen"-Knopf ruft close(), loest kein `cancel` aus Wer nur einen abfaengt, hat eine halbe Sicherung -- und die ist schlimmer als keine, weil man ihr vertraut. NICHT VON HAND VERTEILT: Jeder Dialog mit Feldern bekommt sie automatisch (ausser dem Nachfrage-Dialog selbst -- er wuerde beim Schliessen nach sich selbst fragen, in sich selbst, und haenge fuer immer). Wer morgen einen neunten baut, hat sie, ohne daran zu denken. UND SIE FRAGT NUR BEI WIRKLICHER AENDERUNG. Beim Oeffnen wird ein Abbild der Felder genommen, beim Schliessen verglichen. Wer einen Dialog aufmacht und gleich wieder zu, merkt nichts. ZWEI SACHEN BEIM BAUEN GEMESSEN STATT VERMUTET: MEINE EIGENE "ROBUSTHEIT" HAT DIE WACHE STILL AUSGESCHALTET. Gegen den Fall "Felder werden erst nach dem Oeffnen gefuellt" hatte ich einen zweiten Schnappschuss beim Hineinklicken (`focusin`) eingebaut. Gemessen: Der entstand NACH dem Tippen -- Fokus und Werteingabe passieren im selben Atemzug. `beimOeffnen` war danach gleich dem getippten Text, und der Dialog ging wortlos zu. Die Sicherung sah eingebaut aus und tat nichts. Jetzt wird im naechsten BILD nachgetragen: Was das Skript beim Oeffnen nachtraegt, ist drin; getippt haben kann in derselben Sechzehntelsekunde niemand. (Nachgemessen: Alle acht Dialoge fuellen heute VOR showModal.) ZWEIMAL ESC HINTEREINANDER SCHLIESST TROTZDEM. Das ist Chromiums "close watcher": Eine Seite darf den Nutzer nicht mit Esc einsperren. Die Regel ist richtig; dagegen anzubauen waere falsch. Sie steht deshalb im Code und in der Pruefung -- festgehalten, nicht umgangen. Der Knopf unterliegt ihr nicht, und am Handy gibt es ohnehin kein Esc. Elf Speicherwege rufen `vergessen()`, damit nach erfolgreichem Speichern nicht gefragt wird. Eine Warnung nach dem Speichern waere genau die, die man wegklickt -- und danach auch die echte. pruef-nachfrage 49/0 (war 33), davon zehn am Bildschirm: unberuehrt geht er ohne Nachfrage zu nach einer Aenderung fragt Esc nach, der Dialog bleibt offen "Weiter bearbeiten" laesst ihn offen UND der Text steht noch da auch der Abbrechen-Knopf fragt -- jedes Mal "Verwerfen" schliesst wirklich, und der Text steht nirgends pruef-aufgabenbrett, -leistung, -uebersicht-browser, -chat-optik, -css-klassen gruen, pruef-tippziele 11/0.
This commit is contained in:
@@ -354,6 +354,8 @@
|
||||
$('abbruch-fehler').textContent = sagWas(d.fehler, 'Hat nicht geklappt.');
|
||||
return;
|
||||
}
|
||||
/* Gespeichert -- die Verwurfwache darf hier nicht fragen. */
|
||||
dlg.vergessen?.();
|
||||
dlg.close();
|
||||
await laden();
|
||||
} catch { /* umgeleitet */ } finally { knopf.disabled = false; }
|
||||
@@ -996,6 +998,8 @@
|
||||
$('b-fehler').textContent = sagWas((await a.json().catch(() => ({}))).fehler, 'Speichern ging nicht.');
|
||||
return;
|
||||
}
|
||||
/* Gespeichert -- die Verwurfwache darf hier nicht fragen. */
|
||||
dialog.vergessen?.();
|
||||
dialog.close();
|
||||
await laden();
|
||||
} catch { /* umgeleitet */ } finally { $('b-speichern').disabled = false; }
|
||||
@@ -1014,6 +1018,8 @@
|
||||
try {
|
||||
const a = await hole(`/workspace/api/aufgaben/${bearbeitet.id}`, { method: 'DELETE' });
|
||||
if (!a.ok) { $('b-fehler').textContent = 'Loeschen ging nicht.'; return; }
|
||||
/* Gespeichert -- die Verwurfwache darf hier nicht fragen. */
|
||||
dialog.vergessen?.();
|
||||
dialog.close();
|
||||
await laden();
|
||||
} catch { /* umgeleitet */ }
|
||||
|
||||
@@ -1876,6 +1876,8 @@
|
||||
return;
|
||||
}
|
||||
const raumId = kanalUmbau;
|
||||
/* Gespeichert -- die Verwurfwache darf hier nicht fragen. */
|
||||
$('neu-dialog').vergessen?.();
|
||||
$('neu-dialog').close();
|
||||
await raeumeLaden();
|
||||
await raumOeffnen(raumId, { sanft: true });
|
||||
@@ -1892,6 +1894,8 @@
|
||||
return;
|
||||
}
|
||||
const raumId = (await a.json()).raum_id;
|
||||
/* Gespeichert -- die Verwurfwache darf hier nicht fragen. */
|
||||
$('neu-dialog').vergessen?.();
|
||||
$('neu-dialog').close();
|
||||
await raeumeLaden();
|
||||
await raumOeffnen(raumId);
|
||||
@@ -1931,6 +1935,8 @@
|
||||
}
|
||||
raumId = (await a.json()).raum_id;
|
||||
}
|
||||
/* Gespeichert -- die Verwurfwache darf hier nicht fragen. */
|
||||
$('neu-dialog').vergessen?.();
|
||||
$('neu-dialog').close();
|
||||
await raeumeLaden();
|
||||
await raumOeffnen(raumId);
|
||||
|
||||
@@ -397,6 +397,8 @@
|
||||
$('tag-fehler').textContent = sagWas((await a.json().catch(() => ({}))).fehler, 'Ging nicht.');
|
||||
return;
|
||||
}
|
||||
/* Gespeichert -- die Verwurfwache darf hier nicht fragen. */
|
||||
d.vergessen?.();
|
||||
d.close();
|
||||
await allesLaden();
|
||||
} catch { /* umgeleitet */ }
|
||||
@@ -415,6 +417,8 @@
|
||||
const a = await hole(`/workspace/api/leistung/${offenerCreator}/${d.dataset.tag}`,
|
||||
{ method: 'DELETE' });
|
||||
if (!a.ok) { $('tag-fehler').textContent = 'Ging nicht.'; return; }
|
||||
/* Gespeichert -- die Verwurfwache darf hier nicht fragen. */
|
||||
d.vergessen?.();
|
||||
d.close();
|
||||
await allesLaden();
|
||||
} catch { /* umgeleitet */ }
|
||||
@@ -448,6 +452,8 @@
|
||||
}),
|
||||
});
|
||||
if (!a.ok) { $('ziele-fehler').textContent = 'Ging nicht.'; return; }
|
||||
/* Gespeichert -- die Verwurfwache darf hier nicht fragen. */
|
||||
$('ziele-dialog').vergessen?.();
|
||||
$('ziele-dialog').close();
|
||||
await allesLaden();
|
||||
} catch { /* umgeleitet */ }
|
||||
@@ -628,6 +634,8 @@
|
||||
});
|
||||
const r = await a.json();
|
||||
if (!a.ok) { $('import-fehler').textContent = sagWas(r.fehler, 'Ging nicht.'); return; }
|
||||
/* Gespeichert -- die Verwurfwache darf hier nicht fragen. */
|
||||
$('import-dialog').vergessen?.();
|
||||
$('import-dialog').close();
|
||||
$('datei').value = '';
|
||||
importText = '';
|
||||
@@ -970,6 +978,8 @@
|
||||
$('netz-los').disabled = false;
|
||||
return;
|
||||
}
|
||||
/* Gespeichert -- die Verwurfwache darf hier nicht fragen. */
|
||||
$('netz-dialog').vergessen?.();
|
||||
$('netz-dialog').close();
|
||||
melde(`${d.geschrieben} Zeile(n) übernommen`
|
||||
+ (d.unbekannt?.length ? ` · ${d.unbekannt.length} nicht zugeordnet` : ''));
|
||||
|
||||
@@ -324,3 +324,174 @@
|
||||
});
|
||||
};
|
||||
})();
|
||||
|
||||
/* =====================================================================
|
||||
EINE EINGABE GEHT NICHT WORTLOS VERLOREN — window.verwurfWache()
|
||||
|
||||
Filipe, 19.09.2026: „Was passiert, wenn jemand eine Funktion
|
||||
abbricht?"
|
||||
|
||||
---------------------------------------------------------------------
|
||||
WAS GEMESSEN WURDE
|
||||
|
||||
Acht Dialoge im Workspace enthalten Eingabefelder -- „Aufgabe
|
||||
bearbeiten" (10 Felder), der Tageseintrag in der Leistung (8), Ziele,
|
||||
Netzwerk, Import, ein neuer Chat-Raum, ein Creator.
|
||||
|
||||
Bei allen gilt bis heute dasselbe: Esc drücken, daneben klicken oder
|
||||
auf „Abbrechen" tippen wirft ALLES weg, wortlos. Wer zehn Felder
|
||||
ausgefüllt hat und mit dem Daumen den Rand trifft, fängt von vorn an.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
WARUM BEIDE WEGE ABGEFANGEN WERDEN MÜSSEN
|
||||
|
||||
Ein <dialog> geht auf ZWEI Arten zu, und sie laufen verschieden:
|
||||
|
||||
Esc / Klick daneben -> löst `cancel` aus, DANN schliesst der
|
||||
Browser selbst. Die Methode `close()`
|
||||
wird dabei NICHT aufgerufen.
|
||||
„Abbrechen"-Knopf -> ruft `close()` auf, ohne `cancel`.
|
||||
|
||||
Wer nur `cancel` abfängt, lässt den Knopf durch. Wer nur `close`
|
||||
ersetzt, lässt Esc durch. Beides einzeln wäre eine halbe Sicherung
|
||||
-- und eine halbe Sicherung ist schlimmer als keine, weil man ihr
|
||||
vertraut.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
UND SIE DARF NICHT IMMER FRAGEN
|
||||
|
||||
„Eine Warnung, die immer kommt, ist keine Warnung mehr." Gefragt
|
||||
wird nur, wenn sich WIRKLICH etwas geändert hat: Beim Öffnen wird
|
||||
ein Abbild der Felder genommen, beim Schliessen verglichen. Wer
|
||||
einen Dialog aufmacht und gleich wieder zu, merkt nichts davon.
|
||||
|
||||
AUFRUF (einmal je Dialog, beim Aufbau der Seite):
|
||||
verwurfWache(document.getElementById('bearbeiten-dialog'));
|
||||
===================================================================== */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/** Der Inhalt aller Felder als eine Zeichenkette. Reicht zum
|
||||
* Vergleichen -- es geht nur um „gleich oder nicht". */
|
||||
function abbild(dlg) {
|
||||
return [...dlg.querySelectorAll('input, textarea, select')]
|
||||
.map((f) => (f.type === 'checkbox' || f.type === 'radio')
|
||||
? (f.checked ? '1' : '0')
|
||||
: String(f.value ?? ''))
|
||||
.join('\u0000');
|
||||
}
|
||||
|
||||
window.verwurfWache = function verwurfWache(dlg, o) {
|
||||
if (!dlg || dlg.dataset.wache === 'ja') return;
|
||||
o = o || {};
|
||||
dlg.dataset.wache = 'ja';
|
||||
|
||||
let beimOeffnen = null;
|
||||
|
||||
/* WANN GEHT ER AUF? Das weiss nur der Browser -- die Seite ruft
|
||||
`showModal()` an zwanzig Stellen. Ein Beobachter auf dem
|
||||
`open`-Merkmal braucht dafuer keine einzige Zeile in der Seite. */
|
||||
if (window.MutationObserver) {
|
||||
new MutationObserver(() => {
|
||||
if (dlg.open) beimOeffnen = abbild(dlg);
|
||||
}).observe(dlg, { attributes: true, attributeFilter: ['open'] });
|
||||
}
|
||||
if (dlg.open) beimOeffnen = abbild(dlg);
|
||||
|
||||
/* NOCH EINMAL IM NAECHSTEN BILD.
|
||||
----------------------------------------------------------------
|
||||
Nachgemessen am 20.09.2026: Alle acht Dialoge fuellen ihre
|
||||
Felder VOR `showModal()`. Der Schnappschuss oben stimmt also.
|
||||
|
||||
Er stimmt aber nur, solange das so bleibt. Fuellt jemand morgen
|
||||
ein Feld erst NACH dem Oeffnen, waere der Schnappschuss leer,
|
||||
und die Wache fragte bei JEDEM Schliessen. „Eine Warnung, die
|
||||
immer kommt, ist keine Warnung mehr."
|
||||
|
||||
ERSTER ANLAUF WAR EIN NEUER SCHNAPPSCHUSS BEIM HINEINKLICKEN
|
||||
(`focusin`) -- und der hat die Wache still ausgeschaltet.
|
||||
Gemessen: Der Schnappschuss entstand NACH dem Tippen, weil
|
||||
Fokus und Werteingabe im selben Atemzug passieren koennen.
|
||||
`beimOeffnen` war danach gleich dem getippten Text, `geaendert()`
|
||||
falsch, und der Dialog ging wortlos zu -- die Sicherung sah
|
||||
eingebaut aus und tat nichts.
|
||||
|
||||
Jetzt wird im NAECHSTEN BILD noch einmal abgebildet. Was das
|
||||
Skript beim Oeffnen nachtraegt, ist dann drin; getippt haben
|
||||
kann in derselben Sechzehntelsekunde niemand. */
|
||||
const nachtragen = () => {
|
||||
if (!dlg.open) return;
|
||||
requestAnimationFrame(() => { if (dlg.open) beimOeffnen = abbild(dlg); });
|
||||
};
|
||||
nachtragen();
|
||||
if (window.MutationObserver) {
|
||||
new MutationObserver(nachtragen)
|
||||
.observe(dlg, { attributes: true, attributeFilter: ['open'] });
|
||||
}
|
||||
|
||||
const geaendert = () => beimOeffnen !== null && abbild(dlg) !== beimOeffnen;
|
||||
|
||||
function fragen(dann) {
|
||||
frageNach({
|
||||
titel: o.titel || 'Eingaben verwerfen?',
|
||||
was: o.was || 'Was du hier eingetragen hast, geht verloren.',
|
||||
bleibt: o.bleibt || 'Der Eintrag selbst bleibt unverändert – '
|
||||
+ 'es wird nur nichts übernommen.',
|
||||
ja: o.ja || 'Verwerfen',
|
||||
nein: 'Weiter bearbeiten',
|
||||
}).then((ja) => { if (ja) { beimOeffnen = null; dann(); } });
|
||||
}
|
||||
|
||||
/* Weg 1: Esc und der Klick daneben. `cancel` ist abbrechbar --
|
||||
das ist der einzige Griff, den der Browser dafuer anbietet. */
|
||||
dlg.addEventListener('cancel', (e) => {
|
||||
if (!geaendert()) return;
|
||||
e.preventDefault();
|
||||
fragen(() => dlg.close());
|
||||
});
|
||||
|
||||
/* Weg 2: jeder Aufruf von close() -- also die „Abbrechen"-Knoepfe
|
||||
und alles, was die Seite sonst tut. Ersetzt wird NUR an diesem
|
||||
einen Dialog, nicht am Prototyp: Ein globaler Eingriff traefe
|
||||
auch den Nachfrage-Dialog selbst, und der schlösse sich dann nie.
|
||||
|
||||
Bei unveraendertem Inhalt bleibt es synchron wie vorher -- wer
|
||||
nichts getippt hat, merkt von der Wache nichts. */
|
||||
const echtesSchliessen = dlg.close.bind(dlg);
|
||||
dlg.close = function (...rest) {
|
||||
if (!geaendert()) { beimOeffnen = null; return echtesSchliessen(...rest); }
|
||||
fragen(() => echtesSchliessen(...rest));
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/* Nach dem Speichern ist nichts mehr zu retten -- dann darf der
|
||||
Dialog ohne Nachfrage zugehen. Die Seite meldet das, indem sie
|
||||
`vergessen()` aufruft; ohne Meldung fragt die Wache weiter. */
|
||||
dlg.vergessen = () => { beimOeffnen = abbild(dlg); };
|
||||
};
|
||||
|
||||
/* =====================================================================
|
||||
UND SIE WIRD NICHT VON HAND VERTEILT
|
||||
|
||||
Jeder Dialog mit Eingabefeldern bekommt die Wache -- abgeleitet,
|
||||
nicht aufgezaehlt. Wer morgen einen neunten baut, hat sie, ohne
|
||||
daran zu denken; und genau daran scheitern gepflegte Listen.
|
||||
|
||||
AUSGENOMMEN IST DER NACHFRAGE-DIALOG SELBST. Er hat ein Textfeld
|
||||
(den Grund), und wuerde er sich selbst bewachen, fragte er beim
|
||||
Schliessen nach sich selbst -- und zwar in sich selbst. Der
|
||||
zweite Dialog kann nicht aufgehen, solange der erste offen ist:
|
||||
Er haenge fuer immer.
|
||||
===================================================================== */
|
||||
function alleBewachen() {
|
||||
for (const dlg of document.querySelectorAll('dialog:not(.nachfrage)')) {
|
||||
if (!dlg.querySelector('input, textarea, select')) continue;
|
||||
window.verwurfWache(dlg);
|
||||
}
|
||||
}
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', alleBewachen);
|
||||
} else {
|
||||
alleBewachen();
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user