Öffnungszeiten: Speichern zeichnet nur neu, was sich geändert hat
Ungespeicherte Eingaben in anderen Formularen bleiben stehen: nach dem Speichern wird nur die Plankarte, die Planliste (mit den Eingaben der übrigen Karten), Feiertage oder Ausnahmen neu gezeichnet, „Nein, danke“ entfernt nur die Frage. Während des Speicherns sind auch die Eingabefelder gesperrt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
04a3525f79
commit
f01ed15247
6 files changed
+409
-58
No files matched your search
+32
-33
@@ -8,8 +8,11 @@
|
||||
'feiertage.2026-12-25'); Pfade ohne Platz im container erscheinen allgemein, mit dem Namen des Eintrags davor. Rueckgabe false.
|
||||
- 409 (ZEITEN_VERALTET, jemand hat inzwischen gespeichert): Hinweis mit Knopf „Neu laden“ oben im Editor. Rueckgabe false.
|
||||
- alles andere (429 zu viele Speichervorgaenge, 403, 500, kein Netz): Meldung ueber dem Editor (ctx.meldung). Rueckgabe false.
|
||||
- Es laeuft immer nur ein Speichern (PUT und das Uebernehmen): solange es laeuft, ist der Editor aria-busy, alle seine Knoepfe sind gesperrt, und
|
||||
ein weiterer Aufruf von speichere (z. B. ein Enter im Formular) wird abgewiesen (Rueckgabe false), statt mit einer veralteten Version zu gehen.
|
||||
- Es laeuft immer nur ein Speichern (PUT und das Uebernehmen): solange der PUT laeuft, ist der Editor aria-busy, alle seine Knoepfe UND Eingabefelder
|
||||
sind gesperrt (waehrenddessen Getipptes ginge sonst verloren), und ein weiterer Aufruf von speichere (z. B. ein Enter im Formular) wird abgewiesen
|
||||
(Rueckgabe false), statt mit einer veralteten Version zu gehen. Der Fokus geht danach zum ersten Feld mit Fehler, sonst dorthin zurueck, wo er war.
|
||||
- Nach einem Erfolg zeichnet der Abschnitt nur neu, was das Speichern geaendert hat (nachSpeichern mit neuZeichnen): ungespeicherte Eingaben in den
|
||||
anderen Formularen bleiben stehen. Nur „Neu laden“ nach einem 409 zeichnet den ganzen Editor neu.
|
||||
Der Abschnitt ruft speichere nur, wenn ctx.nurLesen nicht gesetzt ist. Texte des Servers kommen nur als Text in die Seite (h, textContent).
|
||||
ctx (baut zeiten-editor.js): { projectId, stand, aktuell(), neuLaden(), meldung(text, art), uebernimm(antwort, { erstes }) }. */
|
||||
|
||||
@@ -64,7 +67,7 @@ export function beschrifteFehler(pfad, text, quellen) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Fehler des Servers am Feld; was im container keinen Platz hat, steht allgemein (mit dem Namen des Eintrags davor). */
|
||||
/** Fehler des Servers am Feld; was im container keinen Platz hat, steht allgemein (mit dem Namen des Eintrags davor). Den Fokus setzt speichere nach dem Entsperren. */
|
||||
function zeigeFelder(container, fehler, quellen) {
|
||||
const amFeld = {}
|
||||
const rest = []
|
||||
@@ -73,13 +76,12 @@ function zeigeFelder(container, fehler, quellen) {
|
||||
else rest.push(beschrifteFehler(pfad, text, quellen))
|
||||
}
|
||||
setzeFehler(container, rest.length ? { ...amFeld, _: rest.join(' ') } : amFeld)
|
||||
container.querySelector('[aria-invalid="true"]')?.focus()
|
||||
}
|
||||
|
||||
/**
|
||||
* Speicher des Editors. wurzel = das Element des Editors (verschwindet beim Wechsel der Website: danach fasst nichts mehr die Seite an),
|
||||
* neuZeichnen() zeichnet den Editor aus ctx.stand neu (nach „Neu laden“).
|
||||
* => { konflikt, speichere, exklusiv, nochHier }: konflikt ist das Hinweis-Element (der Editor setzt es an den Anfang), nochHier() sagt, ob der
|
||||
* => { konflikt, speichere, nochHier }: konflikt ist das Hinweis-Element (der Editor setzt es an den Anfang), nochHier() sagt, ob der
|
||||
* Editor noch angezeigt wird (vor jedem Anfassen der Seite nach einem await pruefen).
|
||||
*/
|
||||
export function neuerZeitenSpeicher(ctx, wurzel, neuZeichnen) {
|
||||
@@ -102,44 +104,36 @@ export function neuerZeitenSpeicher(ctx, wurzel, neuZeichnen) {
|
||||
}
|
||||
})
|
||||
|
||||
// Ein Speichern zur Zeit: waehrenddessen aria-busy am Editor und alle Knoepfe darin gesperrt (danach wieder frei, der Fokus kehrt zurueck).
|
||||
// Ein Speichern zur Zeit: waehrenddessen aria-busy am Editor und alle Knoepfe und Eingabefelder darin gesperrt (danach wieder frei, der Fokus kehrt zurueck).
|
||||
let laeuft = false
|
||||
function sperre(an, gesperrt) {
|
||||
if (an) {
|
||||
wurzel.setAttribute('aria-busy', 'true')
|
||||
const knoepfe = [...wurzel.querySelectorAll('button:not(:disabled)')]
|
||||
const steuerungen = [...wurzel.querySelectorAll('button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled)')]
|
||||
const fokus = document.activeElement
|
||||
for (const knopf of knoepfe) knopf.disabled = true
|
||||
return { knoepfe, fokus }
|
||||
for (const steuerung of steuerungen) steuerung.disabled = true
|
||||
return { steuerungen, fokus }
|
||||
}
|
||||
wurzel.removeAttribute('aria-busy')
|
||||
for (const knopf of gesperrt.knoepfe) knopf.disabled = false
|
||||
// Der Fokus war auf einem gesperrten Knopf und ist weg: zurueck dorthin; hat der Abschnitt ihn inzwischen anders gesetzt (z. B. auf ein Feld mit Fehler), bleibt das so
|
||||
for (const steuerung of gesperrt.steuerungen) steuerung.disabled = false
|
||||
// Der Fokus war auf einer gesperrten Steuerung und ist weg: zurueck dorthin; hat der Abschnitt ihn inzwischen anders gesetzt (z. B. auf die Meldung), bleibt das so
|
||||
const jetzt = document.activeElement
|
||||
if (gesperrt.knoepfe.includes(gesperrt.fokus) && gesperrt.fokus.isConnected && (!jetzt || jetzt === document.body || jetzt === gesperrt.fokus)) gesperrt.fokus.focus()
|
||||
if (gesperrt.steuerungen.includes(gesperrt.fokus) && gesperrt.fokus.isConnected && (!jetzt || jetzt === document.body || jetzt === gesperrt.fokus)) gesperrt.fokus.focus()
|
||||
return null
|
||||
}
|
||||
|
||||
/** arbeit() exklusiv ausfuehren; laeuft schon etwas, passiert nichts (Rueckgabe undefined). */
|
||||
async function exklusiv(arbeit) {
|
||||
if (laeuft) return undefined
|
||||
laeuft = true
|
||||
const gesperrt = sperre(true)
|
||||
try {
|
||||
return await arbeit()
|
||||
} finally {
|
||||
laeuft = false
|
||||
sperre(false, gesperrt)
|
||||
}
|
||||
}
|
||||
|
||||
async function speichere(aendere, { container }) {
|
||||
const ergebnis = await exklusiv(async () => {
|
||||
if (laeuft) return false // ein Speichern zur Zeit
|
||||
laeuft = true
|
||||
let gesperrt = null
|
||||
let amFeld = false
|
||||
try {
|
||||
setzeFehler(container, {})
|
||||
konflikt.hidden = true
|
||||
const bisher = ctx.stand.daten
|
||||
const entwurf = structuredClone(bisher ?? ctx.leereDaten())
|
||||
if (aendere(entwurf) === false) return false // der Abschnitt hat selbst Fehler am Feld gezeigt
|
||||
gesperrt = sperre(true)
|
||||
try {
|
||||
const antwort = await request(`/api/zeiten/${encodeURIComponent(ctx.projectId)}`, { method: 'PUT', body: { version: ctx.stand.version, daten: entwurf } })
|
||||
if (!ctx.aktuell()) return false
|
||||
@@ -153,16 +147,20 @@ export function neuerZeitenSpeicher(ctx, wurzel, neuZeichnen) {
|
||||
konflikt.scrollIntoView({ block: 'nearest' })
|
||||
} else if (err.status === 400 && Object.keys(err.fehler || {}).length) {
|
||||
zeigeFelder(container, err.fehler, [bisher, entwurf])
|
||||
amFeld = true
|
||||
} else {
|
||||
ctx.meldung(err.message, 'fehler')
|
||||
}
|
||||
return false
|
||||
}
|
||||
})
|
||||
return ergebnis === true
|
||||
} finally {
|
||||
laeuft = false
|
||||
if (gesperrt) sperre(false, gesperrt)
|
||||
if (amFeld && nochHier()) container.querySelector('[aria-invalid="true"]')?.focus() // gesperrte Felder nehmen den Fokus nicht: erst nach dem Entsperren
|
||||
}
|
||||
}
|
||||
|
||||
return { konflikt, speichere, exklusiv, nochHier }
|
||||
return { konflikt, speichere, nochHier }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,11 +174,12 @@ export function stelleVon(liste, id, form, was) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Nach einem erfolgreichen Speichern: Abschnitte neu zeichnen, Meldung zeigen, Fokus auf das Element mit selektor (Regel: nach dem Speichern
|
||||
* auf den Knopf der gespeicherten Zeile, nach dem Entfernen auf den „+“-Knopf der Liste).
|
||||
* Nach einem erfolgreichen Speichern: neu zeichnen, was das Speichern geaendert hat (neuZeichnen(); ohne Angabe der ganze Editor – die Abschnitte
|
||||
* rufen es immer mit der kleinsten noetigen Aenderung auf, damit ungespeicherte Eingaben in anderen Formularen stehen bleiben), Meldung zeigen,
|
||||
* Fokus auf das Element mit selektor (Regel: nach dem Speichern auf den Knopf der gespeicherten Zeile, nach dem Entfernen auf den „+“-Knopf der Liste).
|
||||
*/
|
||||
export function nachSpeichern(t, meldung, selektor) {
|
||||
t.zeichne()
|
||||
export function nachSpeichern(t, meldung, selektor, neuZeichnen = () => t.zeichne()) {
|
||||
neuZeichnen()
|
||||
t.ctx.meldung(meldung)
|
||||
t.wurzel.querySelector(selektor)?.focus()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user