/* Finanzmanager – Frontend-Hilfen. * * Bewusst schlank und ohne Framework. * * Bestätigungen hängen an
und nicht als onsubmit * im HTML: Die Content-Security-Policy erlaubt keine Inline-Skripte, ein * onsubmit-Attribut würde stillschweigend verworfen – und dann löschte der * Knopf ohne Rückfrage. */ (function () { 'use strict'; // Formulare mit data-bestaetigen fragen vor dem Absenden nach. // In der Capture-Phase, damit die Rückfrage auch dann greift, wenn ein // anderer Handler das Ereignis unterwegs stoppt. document.addEventListener('submit', function (ereignis) { const formular = ereignis.target; if (!(formular instanceof HTMLFormElement)) { return; } const frage = formular.dataset.bestaetigen; if (frage && !window.confirm(frage)) { ereignis.preventDefault(); ereignis.stopPropagation(); } }, true); // Kategoriebaum im Budgetplaner auf- und zuklappen. Die Zeilen kennen ihre // Elternkategorie über data-eltern; von dort aus lässt sich der Teilbaum // einsammeln, ohne ihn im HTML zu verschachteln (in einer Tabelle ginge das // ohnehin nicht). document.querySelectorAll('.klappknopf').forEach(function (knopf) { knopf.setAttribute('aria-expanded', 'true'); knopf.addEventListener('click', function () { const wurzel = knopf.dataset.klappt; const offen = knopf.getAttribute('aria-expanded') !== 'false'; knopf.setAttribute('aria-expanded', offen ? 'false' : 'true'); const zeilen = Array.from(document.querySelectorAll('tr.kategoriezeile')); const betroffen = new Set([wurzel]); zeilen.forEach(function (zeile) { const eltern = zeile.dataset.eltern; if (eltern && betroffen.has(eltern)) { betroffen.add(zeile.dataset.kategorie); zeile.classList.toggle('zugeklappt', offen); } }); }); }); // Zahnrad im Budgetplaner: verteilt den eingegebenen Wert über die Zeile, // wahlweise auf das ganze oder nur auf das restliche Jahr. Gearbeitet wird // auf den Eingabefeldern, nicht auf der Datenbank – so ist das Ergebnis vor // dem Speichern zu sehen und geht mit demselben Knopf weg wie alles andere. // // Das Menü gibt es nur einmal und wandert in die angeklickte Zelle: Ein // eigenes Menü je Feld wären zwölf Stück pro Kategoriezeile, und ein frei // positioniertes bräuchte ein style-Attribut, das die // Content-Security-Policy verwirft. const planer = document.querySelector('table.budgetplaner'); if (planer) { const menue = document.createElement('div'); menue.className = 'zahnradmenue'; let feld = null; let zahnrad = null; function schliessen() { if (menue.parentNode) { menue.parentNode.removeChild(menue); } if (zahnrad) { zahnrad.setAttribute('aria-expanded', 'false'); } feld = null; zahnrad = null; } function uebertragen(abMonat) { const zeile = feld.closest('tr'); const wert = feld.value; zeile.querySelectorAll('input.betragsfeld').forEach(function (anderes) { if (Number(anderes.dataset.monat) >= abMonat) { anderes.value = wert; } }); schliessen(); } [ { text: 'Ganzes Jahr', ab: function () { return 1; } }, { text: 'Restliches Jahr', ab: function (monat) { return monat + 1; } } ].forEach(function (eintrag) { const knopf = document.createElement('button'); knopf.type = 'button'; knopf.className = 'menueeintrag'; knopf.textContent = eintrag.text; knopf.addEventListener('click', function () { if (feld) { uebertragen(eintrag.ab(Number(feld.dataset.monat))); } }); menue.appendChild(knopf); }); planer.addEventListener('click', function (ereignis) { const knopf = ereignis.target.closest('.zahnrad'); if (!knopf) { return; } const zelle = knopf.closest('td'); const warOffen = menue.parentNode === zelle; schliessen(); if (warOffen) { return; } feld = zelle.querySelector('input.betragsfeld'); zahnrad = knopf; knopf.setAttribute('aria-expanded', 'true'); zelle.appendChild(menue); menue.firstChild.focus(); }); // Klick daneben oder Escape schließt das Menü wieder. document.addEventListener('click', function (ereignis) { if (!(ereignis.target instanceof Element)) { return; } if (!ereignis.target.closest('.zahnrad, .zahnradmenue')) { schliessen(); } }); document.addEventListener('keydown', function (ereignis) { if (ereignis.key === 'Escape') { schliessen(); } }); } // Empfängerauswahl im Zahlungsverkehr: Die Adresse füllt Name, IBAN und BIC. // Geändert werden darf danach alles – die Auswahl ist eine Abkürzung, keine // Bindung. const adressauswahl = document.querySelector('[data-adressauswahl]'); if (adressauswahl) { adressauswahl.addEventListener('change', function () { const gewaehlt = adressauswahl.selectedOptions[0]; if (!gewaehlt || !gewaehlt.value) { return; } const felder = { empfaenger: gewaehlt.dataset.name, iban: gewaehlt.dataset.iban, bic: gewaehlt.dataset.bic }; Object.keys(felder).forEach(function (name) { const feld = document.getElementById(name); if (feld && felder[name]) { feld.value = felder[name]; } }); }); } // Meldungen nach kurzer Zeit ausblenden – Fehler bleiben stehen. // Über eine Klasse, nicht über element.style: Die Content-Security-Policy // (style-src 'self') verwirft auch per JavaScript gesetzte Inline-Styles, // die Meldung bliebe sonst einfach stehen. document.querySelectorAll('.meldung-erfolg').forEach(function (element) { window.setTimeout(function () { element.classList.add('verblasst'); window.setTimeout(function () { element.remove(); }, 400); }, 5000); }); })();