FluentUI: Öffentliche Helper-API
Zurück: FluentUI (Design-System-Bibliothek)
Die öffentliche PHP-API von wkfluentui: sechs Helper-Klassen liefern framework-freie HTML-Bausteine – Raster, Felder, Schubladen, Leisten. Diese Seite ist ihre Quelle der Wahrheit.
1. Übersicht und Zweck
Status: Stufe 1 – beschreibt tatsächlich umgesetzten Code.
Diese Seite ist die Quelle der Wahrheit für die öffentliche PHP-API von
wkfluentui: sechs eigenständige Helper-Klassen mit zusammen
77 öffentlichen Methoden (widgets 67, formpage 5, nav 2,
adminrail/gallery/user je 1), die framework-freie HTML-Bausteine (Baum,
Tabs, Drawer/Modal, Formular-Raster, Datentabelle, Tree List,
Eingabefelder-Familie, NavBar, Card, CountBadge, Listen-Filter,
Editor-Tab-Strip, Command-Palette, Kontextmenü-Quelle,
Code-Editor-Aktivierung, Toolbar, CSV-Export), Schema-Formular-Seiten
(formpage) sowie ACL-geprüfte
Datenermittlung (Admin-Leiste, Bildergalerie, Cross-Plugin-Navigation,
Avatar-Initialen) bereitstellen. Zielgruppe: jedes DokuWiki-Plugin oder
-Template, das diese Bausteine wiederverwenden will – die API ist bewusst
nicht an premium-navy-ivory gebunden.
Die meisten Methoden haben zusätzlich eine eigene, vertiefte
Komponentenseite mit Demo-Skizze und Feature-Unterseiten; diese Seite
verweist dort hin, statt den Vertrag zu duplizieren (Zuordnung: Tabelle in
Abschnitt 6). Nur drawer(), modal(), rowActions() sowie je eine
Methode in adminrail/gallery/nav/user haben keine andere
Seite – hier steht ihr vollständiger Vertrag.
2. Voraussetzungen
- DokuWiki mit aktiviertem Plugin
wkfluentui(lib/plugins/wkfluentui/). - PHP-Kontext eines laufenden DokuWiki-Requests (die Methoden nutzen globale DokuWiki-Funktionen wie
hsc(),wl(),cleanID(),auth_quickaclcheck()– kein Aufruf außerhalb einer DokuWiki-Instanz möglich). - Für ACL-geprüfte Methoden (
listImages(),getAdminRailItems()): ein bereits initialisierter Nutzer-/ACL-Kontext (Standard in jedem regulären DokuWiki-Request).
3. Konzepte
Sechs getrennte Helper-Klassen, kein gemeinsames helper.php.
wkfluentui hat bewusst kein eigenes Wurzel-helper.php –
plugin_load('helper', 'wkfluentui') liefert deshalb immer null,
lautlos, ohne Fehler oder Warnung. Jede Komponente liegt unter
helper/<component>.php und wird einzeln geladen (Details siehe
Abschnitt 11, Troubleshooting, weiter unten auf dieser Seite) – dieser
Namensfehler blieb in wk-dw-msqlite-plugin mehrere Features lang
unbemerkt, weil DokuWiki selbst keine Warnung für einen unbekannten
Helper-Namen ausgibt.
Escaping-Konvention (gilt für alle Methoden einheitlich): jede
Methode escaped die ihr übergebenen Klartext-Werte (Label, Titel, Href)
selbst über hsc() (schützt gegen CWE-79/XSS). Parameter, deren Name auf
Html endet (controlHtml, bodyHtml, element), sind dagegen
bereits vom Aufrufer erzeugtes, vertrauenswürdiges HTML – der Aufrufer
trägt dort die Escaping-Verantwortung. Eine Ausnahme von der ersten Regel ist
dokumentiert in Abschnitt
„gallery“ unten.
CSS-Gegenstück: die Sektion WIDGETS in wkfluentui/style.css
(site-weit automatisch eingebunden über DokuWikis css_pluginstyles()).
4. Erste Schritte
Minimalbeispiel: Toolbar-Helfer laden und eine einzeilige Aktionsleiste rendern.
/** @var helper_plugin_wkfluentui_widgets $widgets */ $widgets = plugin_load('helper', 'wkfluentui_widgets'); echo $widgets->toolbar([ ['label' => 'Neu', 'element' => '<button type="submit" name="do" value="new">Neu</button>'], ]);
Falsch: plugin_load('helper', 'wkfluentui') (ohne Suffix) – liefert
null, jeder nachfolgende Methodenaufruf löst einen Fatal Error „Call to a
member function … on null“ aus.
5. Verwendung
| Helper | Konsument | Verwendete Methoden |
|---|---|---|
adminrail | lib/tpl/wkbizway/inc/wkbizway-render.php | getAdminRailItems() |
gallery | lib/tpl/wkbizway/inc/wkbizway-render.php | listImages() 1) |
nav | lib/tpl/wkbizway/inc/nav.php | getBlogCategoryNav(), getAcmenuFlyout() 2) |
user | lib/tpl/wkbizway/main.php, inc/wiki-shell.php | avatarInitials() 3) |
widgets | lib/plugins/wksqliteds/dbadmin/*.php | die neun Bestandsmethoden (Table Designer, Object Explorer, „Connect to…“-Dialog); die neu hinzugekommenen Methoden (Eingabefelder-Familie, treeList(), navBar() u. a.) warten auf ihren ersten Konsumenten |
Weitere WvdS-Plugins sind eingeladen, dieselben Helper zu nutzen (siehe FluentUI (Design-System-Bibliothek), Abschnitt „Rolle“).
6. API-Referenz
| Klasse | Datei | plugin_load()-Aufruf | Methode(n) | Vertrag |
|---|---|---|---|---|
helper_plugin_wkfluentui_adminrail | helper/adminrail.php | plugin_load('helper', 'wkfluentui_adminrail') | getAdminRailItems() | unten, Abschnitt „adminrail“ |
helper_plugin_wkfluentui_gallery | helper/gallery.php | plugin_load('helper', 'wkfluentui_gallery') | listImages() | unten, Abschnitt „gallery“ |
helper_plugin_wkfluentui_nav | helper/nav.php | plugin_load('helper', 'wkfluentui_nav') | getBlogCategoryNav(), getAcmenuFlyout() | unten, Abschnitt „nav“ |
helper_plugin_wkfluentui_user | helper/user.php | plugin_load('helper', 'wkfluentui_user') | avatarInitials() | unten, Abschnitt „user“ |
helper_plugin_wkfluentui_formpage | helper/formpage.php | plugin_load('helper', 'wkfluentui_formpage') | loadSchema(), validate(), serialize(), parse(), formFields() | formpage → Schema-Formular-Seiten |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | tree() | treeview → Grundlagen |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | tabs() | tabcontrol → Grundlagen |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | drawer() | unten, Abschnitt „drawer“ |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | modal() | unten, Abschnitt „modal“ |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | propertyGrid() | property-grid → Grundlagen |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | dataGrid() | datagrid → Grundlagen |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | rowActions() | unten, Abschnitt „rowActions“ |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | codeEditor() | editors → codeEditor()-Baustein |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | toolbar() | toolbar → Grundlagen |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | treeList() | treelist → Grundlagen |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | navBar() | navbar → Grundlagen |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | button() | form-fields → Button |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | calendar() | form-fields → Calendar |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | checkbox() | form-fields → Checkbox |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | comboBox() | form-fields → ComboBox |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | dateTime() | form-fields → DateTime |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | dropdown() | form-fields → Dropdown Editor |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | listEditor() | form-fields → List Editor |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | progressBar() | form-fields → ProgressBar |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | exportRows() | datagrid → Data Export |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | countBadge() | widgets → Katalog (CountBadge) |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | card() | widgets → Katalog (Card/Tile) |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | declarativeTable() | widgets → Katalog (DeclarativeTable) |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | listFilter() | widgets → Katalog (InputBox-Filter) |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | editorTabs() | widgets → Katalog (Editor-Tab-Strip) |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | commandPalette() | widgets → Katalog (Command-Palette) |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | workbench() | unten, Abschnitt „workbench“ |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | statusbar() | unten, Abschnitt „statusbar“ |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | accordion() | unten, Abschnitt „accordion“ |
helper_plugin_wkfluentui_coreform | helper/coreform.php | plugin_load('helper', 'wkfluentui_coreform') | render(), trigger(), formOpen(), elements(), formClose() | core-screens → Kernformulare |
adminrail
| Metadatum | Wert |
|---|---|
| Sprache | PHP |
| Namespace | helper_plugin_wkfluentui_adminrail |
| Datei | lib/plugins/wkfluentui/helper/adminrail.php |
| Sichtbarkeit | public |
| Stabilität | stabil (Stufe 1) |
getAdminRailItems(string $currentPage): array – liefert die
ACL-gefilterte, in die drei festen Buckets admin/manager/other
gruppierte und alphabetisch (Fallback: Menü-Sortierindex) sortierte Liste
aller für den angemeldeten Nutzer sichtbaren Admin-Plugins, im selben Format
wie DokuWikis eigene do=admin-Kachelansicht (\dokuwiki\Ui\Admin),
aber flach statt gruppiert und um ein Inline-SVG-icon-Feld sowie ein
active-Flag für $currentPage ergänzt.
| Parameter | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
$currentPage | string | Ja | der page-Request-Parameter des aktuell angezeigten Admin-Screens ($INPUT->str('page')); leerer String auf dem nackten Admin-Index |
Rückgabewert: Array von ['id' => string, 'label' => string, 'href' => string, 'icon' => string, 'active' => bool] je sichtbarem Admin-Plugin. Reihenfolge: Bucket admin → manager → other, innerhalb jedes Buckets alphabetisch nach Label. Leeres Array, wenn der Nutzer kein einziges Admin-Plugin sehen darf – kein Fehler.
Sicherheit: Die Bucket-Zugehörigkeit (welche Plugin-IDs zu admin/
manager zählen) ist ein wörtliches Duplikat der Liste in DokuWikis
eigenem \dokuwiki\Ui\Admin – bewusst, damit dieselbe Installation in
beiden Ansichten dieselbe Gruppierung zeigt; ein Core-Update, das diese Liste
ändert, erfordert einen synchronen Abgleich hier. ACL-Prüfung über
isAccessibleByCurrentUser() je Plugin (CWE-284/Broken Access Control) –
der Aufrufer muss nicht zusätzlich filtern.
Nebenwirkungen: keine (reine Lese-/Enumerationsmethode).
Fehlerverhalten: wirft keine Exceptions. Ein Plugin, dessen
Menü-Icon-Datei fehlt/zu groß/unlesbar ist, erhält icon' ⇒ ''`` — dieselbe
stille Degradation wie in DokuWikis eigenem Ui\Admin::showMenuItem()
(kein Platzhalter-Icon).
$rail = plugin_load('helper', 'wkfluentui_adminrail'); $items = $rail->getAdminRailItems($INPUT->str('page')); foreach ($items as $item) { echo '<a href="' . $item['href'] . '"' . ($item['active'] ? ' class="active"' : '') . '>' . $item['icon'] . hsc($item['label']) . '</a>'; }
gallery
| Metadatum | Wert |
|---|---|
| Sprache | PHP |
| Namespace | helper_plugin_wkfluentui_gallery |
| Datei | lib/plugins/wkfluentui/helper/gallery.php |
| Sichtbarkeit | public |
| Stabilität | stabil (Stufe 1) |
listImages(string $ns, int $thumb = 200): array – liefert alle
lesbaren Bilddateien direkt in $ns (nicht rekursiv, keine
Unter-Namespaces).
| Parameter | Typ | Erforderlich | Standardwert | Beschreibung |
|---|---|---|---|---|
$ns | string | Ja | – | Medien-Namespace; wird intern über cleanID() normalisiert |
$thumb | int | Nein | 200 | Thumbnail-Breite in Pixeln, intern auf 32–1024 begrenzt |
Rückgabewert: Array von
['id' => string, 'full' => string (ml()-URL), 'thumb' => string (ml()-URL), 'name' => string]
je Bild. Leeres Array bei ungültigem/leerem $ns oder fehlendem Leserecht.
Sicherheit – abweichende Escaping-Regel (Ausnahme zur allgemeinen
Konvention aus Abschnitt 3): $ns wird über cleanID() normalisiert
(CWE-22, Path Traversal) und vor dem Dateisystemzugriff per
auth_quickaclcheck("$ns:*") < AUTH_READ geprüft (CWE-284). Das
name-Feld im Rückgabewert ist roh (noNS($id), nicht
hsc()-escaped) – anders als bei jeder anderen Methode dieser API. Der
Aufrufer muss name vor der Ausgabe selbst escapen; full/thumb
sind bereits fertige, in sich sichere URLs (ml()).
Nebenwirkungen: ein Dateisystem-Scan (search() über
$conf['mediadir'], Tiefe 1) je Aufruf – kein Cache.
Fehlerverhalten: wirft keine Exceptions; alle Fehlerfälle (ungültiger
Namespace, fehlendes Leserecht, leerer Namespace) liefern ein leeres Array,
nicht null und nicht false – ein Aufrufer kann immer direkt iterieren.
$gallery = plugin_load('helper', 'wkfluentui_gallery'); foreach ($gallery->listImages('de:blog:media', 300) as $img) { echo '<img src="' . $img['thumb'] . '" alt="' . hsc($img['name']) . '">'; }
nav
| Metadatum | Wert |
|---|---|
| Sprache | PHP |
| Namespace | helper_plugin_wkfluentui_nav |
| Datei | lib/plugins/wkfluentui/helper/nav.php |
| Sichtbarkeit | public |
| Stabilität | stabil (Stufe 1) |
Zwei Methoden, beide null-tolerant: ist das jeweilige Ziel-Plugin deaktiviert/nicht installiert, liefern beide den leeren String statt eines Fehlers.
getBlogCategoryNav(string $rootNs): string– Blog-Kategorie-Flyout-Fragment für einen Blog-Root-Namespace (z. B.de:blog), delegiert anwkblog_tags::getCategoryTree().getAcmenuFlyout(string $rootNs): string– Namespace-Baum-Flyout-Fragment für einen Wiki-Root-Namespace (z. B.de:wiki), delegiert anwkacmenu::renderNavFlyout().
Rückgabewert (beide): HTML-Fragment (eine <li>-Liste), leerer
String wenn das jeweilige Ziel-Plugin fehlt oder keine Daten liefert.
Nebenwirkungen: beide Methoden cachen intern (DokuWikis
\dokuwiki\Cache\Cache, 1800 Sekunden Lebensdauer, CACHE_AGE-Konstante)
– je Namespace UND je Gruppenmitgliedschaft-Set des aktuell angemeldeten
Nutzers (Sicherheitsgrund, s. u.), nicht global. getBlogCategoryNav()
hängt seinen Cache-Eintrag zusätzlich an wkblog_tagss eigene
Speicherdatei (cacheDependencyFile()), damit eine reine Tag-Änderung
(die die Blogseite selbst nicht berührt) den Cache sofort invalidiert statt
bis zu 1800 Sekunden veraltet zu bleiben. getAcmenuFlyout() hängt seinen
Cache-Eintrag zusätzlich an DokuWikis Seitenindex
(data/index/page.idx): der wächst, sobald eine neu angelegte Seite
erstindiziert wird (Taskrunner beim nächsten Seitenaufruf) – neue Seiten
erscheinen damit ohne 30-Minuten-Wartezeit im Flyout. Dokumentierte Grenze:
Löschungen berühren page.idx nicht (die PID-Zeilen bleiben stabil),
eine gelöschte Seite kann daher bis zum Ablauf der Altersgrenze im Flyout
verbleiben.
Sicherheit: Der Cache-Schlüssel enthält bewusst die
Gruppenmitgliedschaft des Nutzers (sortierte @Gruppe-Liste) – ohne diesen
Bestandteil würde ein ACL-gefilterter Navigationsbaum, den ein
privilegierter Nutzer zuerst aufruft, im Cache landen und danach
fälschlicherweise auch einem nicht-privilegierten Nutzer ausgeliefert
(Cache-basiertes ACL-Leck, CWE-284).
Fehlerverhalten: keine Exceptions; fehlendes Ziel-Plugin → leerer String (kein Log-Eintrag, kein Fehler).
$nav = plugin_load('helper', 'wkfluentui_nav'); echo $nav->getBlogCategoryNav('de:blog'); echo $nav->getAcmenuFlyout('de:wiki');
user
| Metadatum | Wert |
|---|---|
| Sprache | PHP |
| Namespace | helper_plugin_wkfluentui_user |
| Datei | lib/plugins/wkfluentui/helper/user.php |
| Sichtbarkeit | public |
| Stabilität | stabil (Stufe 1) |
avatarInitials(string $realName, string $login): string – liefert
ein bis zwei Großbuchstaben-Initialen für einen Avatar-Platzhalter, wenn
keine hochgeladene Avatar-Datei existiert (die Datei-Lookup-Logik selbst
liegt bewusst nicht hier, s. Abgrenzung unten).
| Parameter | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
$realName | string | Ja (darf leer sein) | Anzeigename des Nutzers |
$login | string | Ja | Login-Name, nur als Fallback genutzt |
Algorithmus: erstes + letztes Wort von $realName bei 2+ Wörtern,
erste zwei Zeichen von $realName bei genau einem Wort, erste zwei
Zeichen von $login als Fallback wenn $realName leer ist. Multibyte-
sicher (mb_substr/mb_strtoupper).
Rückgabewert: string, immer nicht-leer, sofern $login nicht
ebenfalls leer ist.
Sicherheit: Die Methode escaped ihre Ausgabe nicht selbst (anders
als die allgemeine Konvention aus Abschnitt 3, die nur für widgets.php
gilt) – $realName stammt aus dem Nutzerprofil und kann theoretisch
beliebige Zeichen enthalten. Der Aufrufer muss den Rückgabewert vor der
Ausgabe selbst hsc()-escapen.
Abgrenzung: bewusst getrennt von der Avatar-Datei-Lookup-Logik in
wvdsavatar/action.php::currentAvatarUrl() – kleine, stabile
String-Logik bleibt hier dupliziert statt eine harte Cross-Plugin-
Laufzeitabhängigkeit für wenige Zeilen einzuführen (dokumentierte,
bewusste Entscheidung, kein Versehen).
$user = plugin_load('helper', 'wkfluentui_user'); $initials = $user->avatarInitials($INFO['userinfo']['name'] ?? '', $_SERVER['REMOTE_USER'] ?? ''); echo '<span class="avatar-placeholder">' . hsc($initials) . '</span>';
drawer
| Metadatum | Wert |
|---|---|
| Sprache | PHP |
| Namespace | helper_plugin_wkfluentui_widgets |
| Datei | lib/plugins/wkfluentui/helper/widgets.php |
| Sichtbarkeit | public |
| Stabilität | stabil (Stufe 1) |
drawer(string $title, string $bodyHtml, string $cancelHref): string
– rechtsseitiges Slide-in-Panel: ein Vollflächen-Scrim (Klick-/Escape-Ziel =
$cancelHref, ein normaler Link, funktioniert dadurch vollständig ohne
JavaScript) plus ein Panel fester Breite. Generalisiert die zuvor in
DbAdmin::renderNewTableDrawer()/renderAddColumnDrawer()/
renderEditDrawer() duplizierte Struktur.
| Parameter | Typ | Beschreibung |
|---|---|---|
$title | string | Drawer-Überschrift, hsc()-escaped |
$bodyHtml | string | vertrauenswürdiges HTML (z. B. ein Formular) – Aufrufer-escaped |
$cancelHref | string | Scrim-/Abbrechen-Ziel-URL, hsc()-escaped |
Erzeugtes Markup: <div class="wk-drawer"> →
<a class="wk-scrim" href="…"> → <div class="wk-drawer__panel" role="dialog" aria-modal="true">
mit <h3>-Titel und $bodyHtml.
Nebenwirkungen: keine (reine Render-Methode, kein State/Request-Zugriff).
Fehlerverhalten: keines – alle drei Parameter sind Pflicht-Strings, ein leerer String ist für jeden Parameter ein gültiger, wenn auch funktional sinnloser Wert (leerer Titel/Body/Scrim-Ziel).
echo $widgets->drawer( 'Neue Tabelle', $widgets->propertyGrid($spaltenFelder), wl($ID, ['ns' => 'tabellen']) );
modal
| Metadatum | Wert |
|---|---|
| Sprache | PHP |
| Namespace | helper_plugin_wkfluentui_widgets |
| Datei | lib/plugins/wkfluentui/helper/widgets.php |
| Sichtbarkeit | public |
| Stabilität | stabil (Stufe 1) |
modal(string $title, string $bodyHtml, string $cancelHref, array $opts = []): string
– zentriertes Dialogfenster, gleiches Scrim-Prinzip wie drawer(), mittig
statt seitlich verankert.
| Parameter | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
$title | string | Ja | Dialog-Überschrift, hsc()-escaped |
$bodyHtml | string | Ja | vertrauenswürdiges HTML – Aufrufer-escaped |
$cancelHref | string | Ja | Scrim-/Abbrechen-Ziel-URL, hsc()-escaped |
$opts['size'] | string | Nein | normal (Default, 640px) oder workbench (1024×768, resize:both, Viewport-Clamp, <1024px vollflächig) |
$opts['context'] | string | Nein | setzt data-wk-dialog-context; scripts/dialog.js merkt die zuletzt gewählte Größe je Kontext in localStorage unter wk-dialog-size:<context> |
Rückwärtskompatibilität: die 3-Parameter-Aufrufform (ohne $opts)
rendert byte-identisch zur Vorversion bis auf ein zusätzliches
tabindex=„-1“ am Panel (programmatisches Fokusziel der Fokusfalle –
ohne JavaScript wirkungslos, kein sichtbarer Unterschied).
Abgrenzung: das Laden eines fremden Admin-Moduls in einen Dialog
läuft nicht über modal(), sondern über den
a[data-wk-dialog-src]-Trigger-Link-Vertrag (der Link navigiert
ohne JavaScript normal und wird nur progressiv zum Inline-Workbench-Dialog)
– Details: admin-layout →
„Fremd-Modul-Aufrufe als Inline-Dialog“.
Nebenwirkungen: keine serverseitig; clientseitig (mit
scripts/dialog.js): Fokusfalle, Escape-Handler, localStorage-
Schreibzugriff bei workbench-Größe mit gesetztem context.
Fehlerverhalten: unbekannter $opts['size']-Wert degradiert
still auf normal (kein Fehler, keine Warnung).
echo $widgets->modal( 'Verbindung herstellen', $widgets->propertyGrid($verbindungsFelder), wl($ID), ['size' => 'workbench', 'context' => 'connect'] );
rowActions
| Metadatum | Wert |
|---|---|
| Sprache | PHP |
| Namespace | helper_plugin_wkfluentui_widgets |
| Datei | lib/plugins/wkfluentui/helper/widgets.php |
| Sichtbarkeit | public |
| Stabilität | stabil (Stufe 1) |
rowActions(array $items): string – verpackt bereits vorhandene,
sichtbare Aktions-Elemente einer Baum-/Grid-Zeile als versteckte
<ul class="wk-row-actions" hidden> (Kontextmenü-Quelle für
scripts/contextmenu.js, s. u.).
| Parameter | Typ | Beschreibung |
|---|---|---|
$items | array | Liste ['label' => string, 'element' => string] – element ist das echte, bereits sichtbar gerenderte <a>-/<button>-HTML |
Rückgabewert: HTML-String; leerer String bei leerem $items (keine
Menü-Anzeige für eine Zeile ohne Aktionen – kein leeres, verwirrendes
Kontextmenü).
Wichtige Invariante: rowActions() dupliziert keine Logik – jedes
element muss identisch mit dem Element sein, das bereits sichtbar in der
Zeile steht. Das Kontextmenü triggert bei Klick .click() auf das echte
Original-Element, es öffnet keinen zweiten Code-Pfad.
Client-Gegenstück scripts/contextmenu.js (progressive Erweiterung):
bei Rechtsklick auf ein Element mit data-wk-menu wird das native
Kontextmenü unterdrückt und ein Popup aus dem benachbarten
.wk-row-actions-Fragment gerendert. Ohne JavaScript bleibt jede
Aktion über die sichtbaren Buttons erreichbar. Einzige Zusatzfunktion ohne
No-JS-Äquivalent: „Copy“/„Copy with Headers“ über
data-wk-copy/data-wk-copy-headers-Attribute
(navigator.clipboard.writeText(…)).
Tastatur (WAI-ARIA-Menu-Muster): Die Einträge tragen
role="menuitem"; beim Öffnen erhält der erste Eintrag den Fokus,
Pfeil hoch/runter wandern zyklisch, Pos1/Ende springen zum
ersten/letzten Eintrag, Escape schließt; beim Schließen kehrt der Fokus
zum nächstliegenden fokussierbaren Element der Ursprungszeile zurück
(dieselbe Mechanik wie die Menüs von scripts/admin-rail.js). Hinweis:
derzeit rendert kein Konsument im lib/-Baum das
data-wk-menu-Attribut – der Vertrag ist gegen das ausgelieferte
Skript (scripts/contextmenu.js) verifiziert, der frühere Erstkonsument
(SQL-Workbench-Ergebniszeilen) setzt es seit seiner Umstrukturierung nicht
mehr.
Nebenwirkungen: keine serverseitig.
Fehlerverhalten: keines – ein element-Wert, der kein valides HTML
ist, wird unverändert ausgegeben (der Aufrufer trägt die Verantwortung für
valides, sicheres HTML in element, s. Escaping-Konvention Abschnitt 3).
$actions = $widgets->rowActions([ ['label' => 'Bearbeiten', 'element' => '<a href="' . wl($ID, ['do' => 'edit']) . '">Bearbeiten</a>'], ['label' => 'Löschen', 'element' => '<button type="submit" name="do" value="delete">Löschen</button>'], ]);
workbench
workbench(array $regions, array $opts = []): string – komponiert die Shell-Regionen in das generische Workbench-Grid .wk-workbench (Layout-Schicht; die Region-Optik bleibt bei den .wk-shell-*-Primitives, die Innenleben bleiben Aufrufer-HTML).
$regions: Schlüsselcommandbar·sidebar·content(Pflicht) ·auxbar·rail·statusbar. Jeder Wert ist ein vertrauenswürdiges Aufrufer-HTML-Fragment (Aufrufer escapet – derselbe Vertrag wiedrawer()/modal()). Fehlende/leere Regionen emittieren kein Element (die Grid-Variante lässt den Track kollabieren statt eine tote Lücke zu lassen). Trägt das äußerste Element des Fragments bereits die Region-Klasse, wird es unverändert durchgereicht (Attribute wieis-collapsedoderdata-wk-auxbar-srcbleiben erhalten); nackte Fragmente werden in das Standard-Element der Region gehüllt.$opts['variant']: '''' (4-spaltig) ·flat·no-sidebar·no-auxbar·auxbar-collapsed·split– muss einem CSS-Archetyp entsprechen; unbekannte Werte fallen auf das volle Grid zurück.splitstellt Content und Auxbar als zwei gleich breite Hälften nebeneinander (minmax(0,1fr) minmax(0,1fr)): der Archetyp für eine zweite, eigenständige Arbeitsfläche neben der ersten – im Unterschied zuno-sidebar, dessen feste 280px-Spalte für „Eigenschaften der Auswahl“ gedacht ist und für eine Arbeitsfläche zu schmal. Unter 1024px stapeln die Hälften; die zweite bleibt sichtbar (sie ist offen, weil der Benutzer sie geöffnet hat – anders als die generische Auxbar, die dort ausgeblendet wird).$opts['class']: zusätzliche Container-Klassen (Klartext, escaped) – der plugin-lokale Scope-Hook (z. B.wkq-shell).$opts['context']: stabiler Flächen-Bezeichner (Klartext, escaped) →data-wk-workbench-context, der Persistenz-Schlüssel für Client-Präferenzen (Sash-Breiten). Ohne Kontext keine Persistenz.$opts['auxbarOpen']:truesagt, dass die Auxbar hält, wonach der Leser gerade gefragt hat – die gewählte Datei, den geöffneten Datensatz. Unter 1280px öffnetscripts/workbench.jssie beim Laden als Overlay, ohne den Fokus zu verschieben; ohne JavaScript fließt sie unter 1024px unter den Inhalt. Wirkt nur, wenn es eine Auxbar gibt. Nicht durchis-openvom Server ersetzen: der Tab-Fang vonworkbench.jsprüft nur diese Klasse und hielte die Tastatur sonst auch bei Desktopbreite in der Spalte fest.$opts['sidebarDocked']:truehält die Seitenleiste bis 600px Fensterbreite als Spalte, statt sie unter 1024px zur Überlagerung zu machen (mit Skript) oder unter 768px auszublenden (ohne). Für ein Werkzeugfenster, dessen Aufgabe das Finden des Orts ist – das Medien-Popup des Editors öffnet mit 750×500, und der Namensraumbaum hinter einem Umschalter machte die Ordnerwahl zu zwei Schritten. Unter 600px bleibt es die Überlagerung wie überall: eine Spalte ließe dem Inhalt dort zu wenig Platz. Wird nur geschrieben, wenn es eine Seitenleiste gibt.$opts['sidebarTitle']: Name der Seitenleiste (Klartext, escaped). Die Region bekommt eine eigene, oben haftende Kopfzeile mit diesem Namen, in der auch der Knopf zum Verkleinern sitzt;scripts/workbench.jsbeschriftet damit auf schmalen Breiten den schwebenden Umschalter der Seitenleiste. Ohne Namen keine Kopfzeile, und der Umschalter bleibt ein unbeschriftetes Symbol.
Verhaltens-Schicht: scripts/workbench.js (Overlay/Scrim/Region-Toggles) und scripts/sash.js (Resize) docken über die Container-Klasse bzw. das Kontext-Attribut an – Regeln in FluentUI: Admin-Bereich (vertieft).
statusbar
statusbar(array $left, array $right = [], array $opts = []): string – Statusbar-Region mit Gruppen-/Item-Anatomie und Prioritäts-Vertrag.
- Item-Form:
['text' => …](Klartext, escaped) oder['html' => …](vertrauenswürdiges Aufrufer-HTML, gewinnt übertext); optional'priority' => int(bereits die Anwesenheit ist das Signal – Items ohne Priorität verschwinden unter 1024px) und'title' => …(Klartext-Tooltip, escaped). - Beide Gruppen-Spans rendern immer (Flex-Anker auch bei leerer Seite); der Mitteltrenner zwischen Items ist dekoratives CSS, nie Textinhalt.
$opts['class']: zusätzliche Container-Klassen (Klartext, escaped).
Warnung: eine Statusbar, deren Items alle keine Priorität tragen, ist unter 1024px leer – die überlebenswichtigen Items immer markieren.
accordion
Seit 2026-07-17 (Design-System-Vereinheitlichung, WDX-WdxAccordion-inspiriert,
s. widgets → Katalog (Accordion)).
accordion(array $items, array $opts = []): string – Progressive-Disclosure-Akkordeon
aus nativen, exklusiven <details name="…">-Gruppen (Öffnen eines Abschnitts
schließt die anderen derselben Gruppe automatisch) – kein JavaScript für die Kernfunktion
nötig.
$items: je Eintrag['summary' => string (Klartext, escaped), 'bodyHtml' => string (vertrauenswürdiges Aufrufer-HTML), 'id' => string (optional, HTML-''id'' des ''<details>''), 'open' => bool (optional, Standard-offener Abschnitt)].$opts['context']: Exklusivitäts-Gruppenname, validiert gegen^[a-z][a-z0-9-]*$(ungültig/fehlend →default). Zweiaccordion()-Aufrufe mit demselben Kontext auf derselben Seite bilden eine Exklusivitätsgruppe über beide Aufrufe hinweg (der Wert wandert 1:1 in<details name="wk-accordion-<context>">; HTML gruppiert pernameunabhängig von der DOM-Position) – nützlich, wenn ein Aufrufer mehrereaccordion()-Blöcke rendert, die trotzdem gemeinsam exklusiv bleiben sollen (Erstkonsument:wkfluentui/action/admintoc.php, ein Akkordeon-Block jegetTOC()-Abschnitt, gemeinsamer Kontextadmintoc).
Rückgabewert: string – <div class="wk-accordion">…</div>. Leerer String bei leerem $items.
Kompatibilität: <details name>-Exklusivität landete in Chrome/Edge 120, Firefox 122,
Safari 17.2 (~2023/24). Ältere Browser ignorieren die Gruppierung und lassen jeden Abschnitt
unabhängig offen – identisch zu einer einfachen <details>-Liste ohne name, kein
Funktionsverlust, nur ohne die Exklusivitäts-Politur.
$widgets = plugin_load('helper', 'wkfluentui_widgets'); echo $widgets->accordion([ ['summary' => 'Sprache', 'bodyHtml' => $langFieldsetHtml, 'id' => 'lang', 'open' => true], ['summary' => 'Titel', 'bodyHtml' => $titleFieldsetHtml, 'id' => 'title'], ], ['context' => 'admintoc']);
JS-Verträge: wkToast, Sash, Dichte
wkToast(type, text, opts)(scripts/toast.js):type∈info/success/warning/error(unbekannt →info);textist Klartext und wird nie als HTML interpretiert;opts.timeoutin ms (Default 6000;errorignoriert ihn und bleibt bis zum Schließen); Rückgabe ist eine Dismiss-Funktion. Nur für asynchrone JS-Rückmeldungen –msg()bleibt der Kanal für Vollseiten-POSTs.- Sash (
scripts/sash.js): injiziert je vorhandener Sidebar/Auxbar einerole="separator"-Leiste; schreibt--wk-workbench-<region>-widthinline auf den Container; Persistenz unterwk-workbench-size:<context>:<region>(re-geklemmt: Sidebar 180–480px, Auxbar 200–520px). - Dichte:
body[data-wk-density="compact"]ist der einzige Schalter der Wirkschicht (Sektion DENSITY instyle.css); wer den Umschalter stellt (Template-User-Menü) und wie persistiert wird (wk-density:<login>), gehört dem Template.
Der Verwaltungsbildschirm: listView und was dazugehört
Seit dem 20.09.2026 folgen alle Verwaltungsbildschirme einer Anordnung, und der
Bausatz liefert sie als einen Aufruf. Die Regel, wem welcher Ort gehört,
steht in docs/how-to-admin-screen.md.
listView(array $opts)– die Anordnung von oben nach unten:noticeHtml(Ausgang der letzten Handlung),views/active/viewsLabel(die Nebenansichten als Reiter, als benanntes<nav>),commands/commandsLabel(Befehle der Liste),filtersHtml(was die Liste einengt),bodyHtml(der Arbeitsvorrat, meist ein füllendes Raster),drawersHtml(Anlegen, Bearbeiten, Ansehen – geschlossen, bis ein Befehl sie öffnet). Nur der Rumpf nimmt die Resthöhe; ein Raster darin rollt seine Zeilen unter einem stehenden Kopf.prose($html, $opts)– ein Block Fließtext auf einer Werkbankfläche. Hält das Zeilenmaß (68 Zeichen) und schneidet den ersten und letzten Abstand ab. Leerer Text ergibt nichts. Für fertig gerendertes Markup, nie für Werte aus einer Anfrage.menuButton($label, array $elements, array $opts)– verwandte Befehle hinter einem Knopf; mitprimarywird daraus ein geteilter Knopf (die übliche Handlung neben dem Menü ihrer Spielarten).rowMenu(array $elements, array $opts)– das Kebab hinter dem Benennungsfeld einer Zeile. Die Einträge sind fertige Bedienelemente des Aufrufers;labelbenennt die Zeile für den zugänglichen Namen.
Dazu kamen vier Angaben an bestehenden Bausteinen:
button(['badge' => '23', 'badgeLabel' => '…'])– die Zahl am Knopf, aus demselbencountBadge()wie am Reiter.textBox/textArea(['mono' => true])– der Inhalt ist Maschinentext (Protokollzeile, Pfad, Schlüssel).textArea(['fill' => true])– das Feld nimmt die Resthöhe seiner Fläche, wie ein füllendes Raster.form(['inline' => true])– ein Formular, das liegt: die Filterleiste über einer Liste.dataGrid([... 'selectionName' => 'sel', 'selectionForm' => 'wkq-bf'])– wie die angehakten Zeilen auf der Leitung heißen und mit welchem Formular sie abgeschickt werden. Für Bildschirme, deren Gegenstelle älter ist als das Raster.
detailLayout
Die Ansicht eines einzelnen Datensatzes: eine Hauptspalte für das, was man liest –
Beschreibung, Unterhaltung –, und eine Seitenspalte für das, was man nachschlägt – die
Eigenschaften als propertyGrid(). Das ist die Anordnung, in der die
Vorbild-Werkbänke ein Arbeitselement zeigen.
$w->detailLayout([ 'main' => $beschreibungUndKommentare, // vertrauenswürdiges HTML 'aside' => $w->propertyGrid($zeilen, ['mode' => 'display']), 'asideLabel' => 'Felder', // Name der Seitenspalte 'stackBelow' => 720, // Vorgabe ]);
- Gestapelt wird nach der Breite des Behälters, nicht des Fensters. Eine solche Ansicht steht oft in der Lesespalte des Wikis; eine Fensterabfrage würde dort zwei Spalten erzwingen, wo nur Platz für eine ist. Gemessen wird wie bei
propertyGrid()überdata-wk-stack-belowundscripts/adaptive.js. Ohne Skript gilt unter 768 px Fensterbreite als Boden immer eine Spalte. - Die Seitenspalte hat eine feste Breite aus
--wk-detail-aside-width(320 px), die Hauptspalte nimmt den Rest. - Die Seitenspalte ist ein benanntes
<aside>und steht im Dokument nach der Hauptspalte – die Lesereihenfolge ist in beiden Anordnungen dieselbe. - Ohne Seitenspalte entsteht eine einzelne Spalte statt einer leeren, die 320 px belegt.
- Die Fläche stellt die Schrift des Bausatzes her (
--wk-fs-body/--wk-lh-body).
Erster Verbraucher: die Arbeitselement-Ansicht (wkdoadowi). Gemessen auf live: 527 px + 320 px in der Lesespalte bei 1400 px Fenster, eine Spalte bei 390 px.
recordHeader
Der Kopf eines Datensatzes: eine kleine Kennzeile (was es ist), darunter der Titel, rechts die Befehle. Das ist die Anatomie, in der die Vorbild-Werkbänke einen einzelnen Datensatz eröffnen – das Arbeitselement in Azure DevOps, das Objekt in Azure Data Studio.
$w->recordHeader([ 'kickerHtml' => $symbol . '<span>User Story 2228</span>', // vertrauenswürdiges HTML 'titleField' => $w->textBox([... , 'heading' => true, 'bare' => true, 'form' => 'mein-formular']), 'title' => 'Ohne titleField: der Titel als Klartext', 'commandsHtml' => $w->toolbar($befehle, ['variant' => 'bare']), 'metaHtml' => '<span>1602 gefunden</span>', 'sticky' => true, // Vorgabe 'stackBelow' => 560, // Vorgabe ]);
- Wer bearbeiten darf, bearbeitet den Titel an seiner Stelle.
titleFieldnimmt ein Eingabefeld (textBoxmit'heading' => true) und stellt es dorthin, wo sonst der Titel steht – ein Titel, nicht ein Titel und darunter ein Feld mit demselben Text. Das Feld liegt in der<h1>, damit die Überschriftennavigation eines Vorleseprogramms den Datensatz weiter findet. - Das Feld sieht erst beim Anfassen wie eines aus: Überschriftgröße (
--wk-fs-heading/--wk-lh-heading), rahmenlos, Rahmen beim Überfahren, Fokusring beim Fokus. Ein Feld, das nie wie eines aussieht, wird nicht als bearbeitbar erkannt; eines, das immer so aussieht, macht den Kopf zum Formular. - Der Kopf klebt oben (
position: sticky) in einem rollenden Behälter, damit die Befehle – allen voran Speichern – erreichbar bleiben, während man in einer langen Beschreibung arbeitet.'sticky' => falsefür Bildschirme, die nicht rollen. - Gestapelt wird nach der Breite des Behälters (
data-wk-stack-below,scripts/adaptive.js): die Befehle gehen unter den Titel. - Titelfeld und Befehle stehen vor dem Formular; sie gehören ihm über das HTML-Attribut
form. Ein zweites Speichern im Formular wäre ein zweiter Knopf für dieselbe Sache.
Erster Verbraucher: die Arbeitselement-Ansicht, ihre Liste und ihr Anlegeformular (wkdoadowi).
8. Vollständige Beispiele
Admin-Rail plus Avatar-Initialen in einer Template-Kopfzeile kombiniert (realistischer Ausschnitt, wie ihn ein Template nach Merge des Refactor-Branches einsetzen würde):
<?php /** @var helper_plugin_wkfluentui_adminrail $rail */ $rail = plugin_load('helper', 'wkfluentui_adminrail'); /** @var helper_plugin_wkfluentui_user $userHelper */ $userHelper = plugin_load('helper', 'wkfluentui_user'); global $INFO, $INPUT; ?> <nav class="wk-shell-rail"> <?php foreach ($rail->getAdminRailItems($INPUT->str('page')) as $item): ?> <a href="<?php echo $item['href']; ?>"<?php echo $item['active'] ? ' class="active"' : ''; ?> aria-current="<?php echo $item['active'] ? 'page' : 'false'; ?>"> <?php echo $item['icon']; ?> <span class="a11y"><?php echo hsc($item['label']); ?></span> </a> <?php endforeach; ?> </nav> <div class="wk-avatar-placeholder"> <?php echo hsc($userHelper->avatarInitials($INFO['userinfo']['name'] ?? '', $INFO['userinfo']['login'] ?? '')); ?> </div>
9. Einschränkungen und Randfälle
- Kein Helper dieser API greift auf
$_REQUEST/$_POSTzu oder verändert Zustand — reine Render-/Enumerations-Methoden. Formular-Verarbeitung (POST-Handling,sectok-Prüfung) bleibt vollständig Sache des Aufrufers. navs Cache-Lebensdauer (1800s) ist fest verdrahtet (CACHE_AGE-Konstante), nicht konfigurierbar über$opts— eine Änderung erfordert einen Code-Eingriff.gallery::listImages()ist nicht rekursiv — Bilder in Unter-Namespaces von$nswerden nicht gefunden, das ist bewusst so (keine versehentliche Tiefenexplosion bei großen Medienbäumen).
10. Accessibility und Kompatibilität
- PHP: kompatibel mit der PHP-Mindestversion von DokuWiki selbst (keine über den Core hinausgehenden Sprachfeature-Anforderungen in dieser API).
- Browser/JavaScript: jede Methode dieser API liefert funktionsfähiges Markup ohne JavaScript (Grundsatz, s. Abschnitt 3/Konzepte); die zugehörigen
scripts/*.js-Dateien sind ausnahmslos progressive Erweiterungen, nie Voraussetzung. - Barrierefreiheits-Details je Methode stehen in ihrem jeweiligen Abschnitt oben bzw. — für die Methoden mit eigener Komponentenseite (
tree(),tabs(),propertyGrid(),dataGrid(),treeList(),toolbar(),navBar(),codeEditor()und die Eingabefelder-Familie) — auf deren dedizierten Seiten (Verweise: Abschnitt 6).
11. Troubleshooting
Symptom: Fatal error: Call to a member function toolbar() on null
(oder jede andere Methode dieser API) direkt nach plugin_load('helper',
'wkfluentui').
Ursache: wkfluentui hat kein Wurzel-helper.php — der Aufruf
ohne Komponenten-Suffix liefert null, lautlos, ohne eigenen Fehler
(s. Abschnitt 3). Dieser Namensfehler blieb in wk-dw-msqlite-plugin
längere Zeit unbemerkt, weil DokuWiki selbst keine Warnung dafür ausgibt.
Lösung: plugin_load('helper', 'wkfluentui_<component>') mit dem
korrekten Suffix aufrufen — widgets, adminrail, gallery, nav,
user oder formpage (Tabelle in Abschnitt 6).
12. Verwandte Themen
- FluentUI (Design-System-Bibliothek) — Bibliotheks-Übersicht, Rollen-Einordnung
- FluentUI: Basis-Widget-Katalog — Widget-Katalog mit VSCode-/ADS-Herkunftszuordnung (inkl. der umgesetzten Bausteine CountBadge, Card/Tile, DeclarativeTable, SplitView, Tooltip)
- FluentUI: Desktop-DataGrid – Demo-Übersicht · FluentUI: Vertical Grid (Property Grid) – Demo-Übersicht · FluentUI: TreeView – Demo-Übersicht · FluentUI: Tree List – Demo-Übersicht · FluentUI: Tab Control – Demo-Übersicht · FluentUI: Toolbar – Demo-Übersicht · FluentUI: NavBar – Demo-Übersicht · FluentUI: Eingabefelder – Demo-Übersicht · FluentUI: Editoren und Code-Darstellung — vertiefte Demo-Portale der Methoden mit eigener Seite
- FluentUI: Admin-Bereich (vertieft) — Verhaltens-Verträge von
scripts/dialog.js/scripts/auxbar.js/scripts/admin-rail.js