1
0
Files
breadcrumb-the-shire/docs/frontend-javascript.md
2026-03-05 11:17:42 +01:00

11 KiB
Raw Blame History

Frontend JavaScript

Letzte Aktualisierung: 2026-03-04

Ordnerstruktur

  • /web/js/core
    • technische Basismodule (DOM/Factories)
  • /web/js/components
    • wiederverwendbare UI-Features
  • /web/js/pages
    • seitenspezifische Hilfen (z. B. Listeninitialisierung)

Initialisierung

  • /web/js/app-boot.js
    • wird im <head> geladen
    • setzt früh UI-States (no-js/js, Sidebar-/Contrast-LocalStorage)
  • /web/js/app-init.js
    • Modul-Entrypoint für Standardseiten
    • importiert Komponenten zentral
  • /web/js/app-login-init.js
    • reduzierter Entrypoint für Loginseiten

Modulkonvention (components/pages)

Empfohlenes Muster:

  1. init...() Funktion exportieren
  2. Früher Guard auf fehlende Elemente
  3. Keine stillen Fehler verschlucken
  4. Nur DOM/UI-Verhalten, keine Fachlogik

Template für neue Komponenten:

  • bestehende Komponenten als Referenz verwenden (/web/js/components/*.js)

Page Module Contract (Hard Cut)

Für seitenbezogene Initialisierung gilt verbindlich:

  • Kein inline <script type="module"> in pages/** oder templates/**.
  • Pro Seite genau ein externes Modul unter web/js/pages/* laden.
  • Serverseitige Daten für dieses Modul über <script type="application/json" id="page-config-...">...</script> bereitstellen.
  • Page-Module lesen Config zentral über readPageConfig(...) aus web/js/core/app-page-config.js.
  • JS-src in pages/** und templates/** nur über assetVersion('...js...').

Defensive DOM-Nutzung

Nicht jede Seite hat jedes Element.

  • Vor Zugriff immer Element-Existenz prüfen.
  • Optional zentrale Helper (app-dom.js) verwenden.
  • Module sollen auf nicht-passenden Seiten sauber no-op sein.

Grid.js-Integration

  • Gemeinsame Initialisierung über Core/Page-Utilities.
  • Vendor-spezifische CSS-Anpassungen in:
    • /web/css/vendor-overrides/gridjs.css
  • Bei dynamischen Rows mit Lightbox/Tooltips: nach Render Refresh triggern.

Grid URL State Sync

createServerGrid(...) synchronisiert bei urlSync: true den Grid-Zustand zentral in die URL.

  • Search + Filter (bestehendes Verhalten)
  • Sortierung über order + dir
  • Pagination über page (1-basiert)

Optionale Konfiguration:

createServerGrid({
  // ...
  urlSync: true,
  urlState: {
    pageParam: 'page',
    sortOrderParam: 'order',
    sortDirParam: 'dir',
    cleanFirstPage: true,
    syncPage: true,
    syncSort: true
  }
});

Hinweise:

  • page=1 wird bei cleanFirstPage: true nicht in der URL gehalten.
  • Übernommene URL-Werte werden validiert:
    • page >= 1
    • dir nur asc|desc
    • order nur bekannte Sort-Key-Spalten
  • Bei Search-/Filter-Änderung wird auf Seite 1 zurückgesetzt.

Row Interaction (Globaler Standard)

Listen mit rowDblClick.getUrl(...) nutzen standardmäßig eine zugängliche Row-Interaktion:

  • sichtbare Action-Spalte rechts (Open/Edit)
  • Doppelklick bleibt als Fallback aktiv
  • Enter auf fokussierter Zeile navigiert zur selben URL

Optionale Konfiguration über createServerGrid(...):

createServerGrid({
  // ...
  rowDblClick: {
    getUrl: (rowData) => new URL(`admin/users/edit/${rowData?.cells?.[1]?.data}`, appBase).toString(),
  },
  rowInteraction: {
    enabled: true,
    showActionButton: true,
    keepDblClick: true,
    enableEnterOnRow: true,
    actionColumnLabel: 'Actions',
    resolveActionLabel: (url) => (url.includes('/edit/') ? 'Edit' : 'Open'),
  }
});

Defaults:

  • rowInteraction.enabled: true, wenn rowDblClick.getUrl vorhanden ist
  • showActionButton: true
  • keepDblClick: true
  • enableEnterOnRow: true
  • actionColumnLabel: language.actions.column oder 'Actions'

Hinweis:

  • Falls eine Seite actions.enabled: true explizit setzt, wird keine zusätzliche automatische Row-Action-Spalte injiziert.

Filter UX 2.0 (Globaler Standard)

Für Listen gilt standardmäßig:

  1. Quick Search sichtbar außerhalb des Drawers.
  2. Restliche Filter im rechten Drawer (data-filter-drawer-*).
  3. Drawer arbeitet apply-first (Draft bis Apply).
  4. Aktive Filter werden als Chips angezeigt (data-active-filter-chips).
  5. Filter-Reset erfolgt über Chips (Clear all filters), nicht im Drawer.

JS-Verkabelung:

  • Standard-Entrypoint ist initStandardListPage(...) aus web/js/core/app-grid-factory.js.
  • Der Wrapper kapselt intern createServerGrid(...) + (bei mode: 'drawer') initListFilterExperience(...).
  • Bei Apply/Clear wird URL mit pushState fortgeschrieben.
  • Save-Filter-Usecases lesen den angewandten Zustand über gridConfig.baseUrl().
  • Chip-State-Logik ist zentral im Wrapper verdrahtet (Default: app-list-filter-state.js).

Search-only-Ausnahme:

  • Listen ohne zusätzliche Filter initialisieren über denselben Wrapper mit mode: 'search-only' (ohne Drawer/Chips).

Standard-List-API

Für Listen-Templates gilt:

  • initStandardListPage({ grid, filters, hooks }) verwenden.
  • grid.filterSchema serverseitig aus filter-schema.php (gridSchemaClientFilters(...)) übergeben.
  • Optional zusätzliche Spezialfilter über grid.extraFilters.
  • Bei Drawer-Listen filters.mode: 'drawer', bei Search-only filters.mode: 'search-only'.

Regel:

  • keine direkten Aufrufe von gridFiltersFromSchema(...) in Listen-Templates
  • keine direkten Aufrufe von initListFilterExperience(...) in Listen-Templates
  • keine inline filters: [...]-Arrays in Listen-Templates
  • MultiSelect-Filter immer als CSV-Key (*_ids, actions, event_types etc.)

Serverseitig passendes Template-Markup:

  • normale Select-Filter über selectFilter(...)
  • MultiSelect-Filter über multiSelectFilter(...)

List Titlebar Standard

Für Listen-Titlebars gilt:

  • Titlebar über templates/partials/app-list-titlebar.phtml rendern.
  • Titel über $listTitle setzen.
  • Aktionen über $listTitleActionsHtml als Slot (captured HTML) übergeben.

Beispiel:

<?php
$listTitle = t('Users');
ob_start();
?>
<a role="button" class="primary" href="admin/users/create"><?php e(t('Create user')); ?></a>
<?php
$listTitleActionsHtml = ob_get_clean();
require templatePath('partials/app-list-titlebar.phtml');
?>

List Tabs Standard

Für Tenant- oder Kontext-Tabs in Listen gilt:

  • Tabs über templates/partials/app-list-tabs.phtml rendern.
  • Tab-Items über $listTabsItems mit href, label, active übergeben.
  • Optional id über $listTabsId setzen.

List Purge Action Standard

Für Purge-Buttons in Titlebar-Actions gilt:

  • Purge-Markup über templates/partials/app-list-purge-action.phtml rendern.
  • Formular/CSRF/Button/Confirm werden zentral übergeben via:
    • $listPurgeEnabled
    • $listPurgeFormId
    • $listPurgeAction
    • $listPurgeConfirmMessage
    • $listPurgeButtonLabel

Aside Grouped Navigation + Persisted Details State

Für gruppierte Sidebar-Navigation mit einklappbaren details gilt:

  • Zentraler Storage-Helper: web/js/core/app-details-open-state.js (initPersistedDetailsGroup(...)).
  • Standardgruppen über details[data-details-key].
  • Persistenz-Aktivierung über:
    • data-details-storage="..." (allgemein, z. B. Detailseiten)
    • data-aside-details-storage="..." (Aside-Panels)
  • Optionales Aktiv-Verhalten im Aside über data-aside-details-open-active="1":
    • aktive Gruppe bleibt immer offen
    • effektive Open-Keys = persistedOpenKeys activeOpenKeys

Admin-Aside-Standard:

  • Storage-Key: aside-admin-sections-v1
  • Gruppenkeys:
    • admin-organization
    • admin-roles-permissions
    • admin-automation
    • admin-monitoring
    • admin-logs
    • admin-system

Detail Page Standard

Für Create/Edit-Detailseiten gilt:

  • Hauptformular über data-standard-detail-form="1" explizit opt-in markieren.
  • Initialisierung läuft zentral über initStandardDetailPage(...) aus web/js/core/app-detail-page-factory.js.
  • Auto-Init erfolgt über web/js/components/app-standard-detail-page.js (in app-init.js eingebunden).

Standardverhalten:

  • Dirty-State über serialisierte FormData-Diffs (initial vs. aktuell).
  • beforeunload-Warnung nur bei ungespeicherten Änderungen.
  • Back/Cancel-Link in .app-details-titlebar h1 a fragt bei Dirty-State per Confirm.
  • Cmd/Ctrl+S triggert primären Save/Create-Button (data-detail-save-primary="1").
  • Submit-Lock/Confirm laufen zentral über die Detail-Action-Policy.
  • Bei Dirty-State wird in der Titlebar automatisch ein kleiner Hinweis mit Feldanzahl gezeigt.

Detail Action Policy

Für standardisierte Detailseiten gilt zusätzlich:

  • initStandardDetailPage(...) bindet intern initDetailActionPolicy(...) aus web/js/core/app-details-action-policy.js.
  • Aktivierung pro Seite über data-detail-action-policy="1" auf .app-details-titlebar (Default im Shared-Partial aktiv).
  • Confirm nicht mehr inline per onclick/onsubmit, sondern über data-detail-confirm-message.
  • Aktionssemantik über data-detail-action-kind (z. B. save, save-close, delete, danger).
  • Optionaler Lock-Override über data-detail-submit-lock="none" (Default: form).

Global Confirm Actions

Für allgemeine Form-/Button-Confirms außerhalb der Detail-Policy gilt:

  • zentral über web/js/components/app-confirm-actions.js
  • Confirm-Text über data-confirm-message
  • keine inline onclick="return confirm(...)" / onsubmit="return confirm(...)" verwenden

Detail Validation Summary Standard

Für serverseitige Formularfehler in Create/Edit-Templates gilt:

  • Fehlerausgabe über templates/partials/app-details-validation-summary.phtml.
  • Keine inline <div class="notice" data-variant="error">-Blöcke mit foreach ($errors as $error) mehr.
  • Standardtitel kommt aus t('Please review the following errors').
  • In Action-Handlern strukturierte Feld-Errors als $validationSummaryErrors = $errorBag->toArray(); bereitstellen.

Template-Beispiel:

<?php
$validationSummaryErrors = $validationSummaryErrors ?? ($errors ?? []);
require templatePath('partials/app-details-validation-summary.phtml');
?>

Optional strukturierte Einträge:

  • ['message' => '...', 'fieldName' => 'email']
  • ['message' => '...', 'fieldId' => 'email']
  • ['email' => ['Required']] (FormErrors-ähnliche Map)

JS-Verhalten über initStandardDetailPage(...):

  • Klick auf Validation-Link fokussiert das passende Feld.
  • Bei vorhandener Summary wird initial das erste betroffene Feld (oder Summary) fokussiert.
  • Betroffene Felder werden zentral mit aria-invalid="true" und aria-describedby (Summary-ID) markiert.

Laufzeit-Check (ohne Node)

Prüfen im Browser:

  1. DevTools öffnen (Console, optional Preserve log aktivieren).
  2. Kernseiten laden (/admin/users, /address-book, Audit-Listen).
  3. Filter setzen/zurücksetzen, Row-Action-Button + Enter + Doppelklick sowie Bulk-Aktionen testen.
  4. Erwartung: keine JavaScript-Errors und keine Unhandled Promise Rejections.