TreeView: Grundlagen
Zurück: Demo-Übersicht
1. Übersicht und Zweck
Status: [Live]. Quelle:
lib/plugins/wkfluentui/helper/widgets.php, Methode tree()
(Zeile 44).
tree() rendert einen rekursiven Navigationsbaum aus verschachtelten
<details>/<summary>-Elementen — vollständig ohne JavaScript
bedienbar. Zielgruppe: jedes Plugin, das eine hierarchische Seiten-/Objekt-
Navigation braucht (z. B. Sidebar-Objektbaum), ohne eine eigene
Tastatur-/Auf-Zuklapp-Logik zu bauen.
Wichtige Abgrenzung: tree() ist ein Navigationsbaum — jeder
Knoten mit href ist ein Link auf eine andere Seite, kein
clientseitig auswählbarer Datenbaum mit eigenem Zustand (dafür:
Selection /
Checkboxes).
2. Voraussetzungen
- DokuWiki mit aktiviertem Plugin
wkfluentui. helper_plugin_wkfluentui_widgetsgeladen überplugin_load('helper', 'wkfluentui_widgets').
3. Konzepte
Native Semantik statt ARIA-Nachbau: <details>/<summary>
liefert Auf-/Zuklappen, Tastaturbedienung (Enter/Leertaste) und die
implizite Screenreader-Semantik kostenlos — kein role="tree"-Nachbau
nötig, solange keine Auswahl aktiv ist (s.
Accessibility
für den Unterschied, sobald Selection dazukommt).
Rekursion: ein Knoten mit children wird selbst wieder über tree()
gerendert — dieselbe Methode baut beliebig tiefe Hierarchien auf.
4. Erste Schritte
/** @var helper_plugin_wkfluentui_widgets $widgets */ $widgets = plugin_load('helper', 'wkfluentui_widgets'); echo $widgets->tree([ ['label' => 'Verbindungen', 'href' => wl($ID, ['ns' => 'connections'])], [ 'label' => 'Tabellen', 'children' => [ ['label' => 'aufgaben', 'href' => wl($ID, ['t' => 'aufgaben']), 'active' => true], ['label' => 'benutzer', 'href' => wl($ID, ['t' => 'benutzer'])], ], ], ]);
Falsch: einen Knoten ohne href UND ohne children anlegen, in der
Erwartung, dass er als klickbarer Platzhalter erscheint. Ohne beides rendert
der Knoten als reiner, nicht interaktiver Text — das ist die korrekte,
dokumentierte Degradation, kein Bug.
5. Verwendung
| Einsatz | Muster |
|---|---|
| Sidebar-Objektbaum | tree() direkt in der Sidebar-Region — Erstkonsument wksqliteds |
| Aktueller-Seite-Markierung | active ⇒ true am passenden Blattknoten setzen |
| Kontextdaten am Link | dataSql für frei nutzbare data-sql-Attribute (SQL-Workbench-Konvention) |
6. API-Referenz
| Metadatum | Wert |
|---|---|
| Sprache | PHP |
| Namespace | helper_plugin_wkfluentui_widgets |
| Datei | lib/plugins/wkfluentui/helper/widgets.php |
| Sichtbarkeit | public |
| Stabilität | stabil (Tier 1) |
public function tree(array $nodes): string
Zusammenfassung: Rendert einen rekursiven, JavaScript-freien Navigationsbaum aus verschachtelten Knoten-Definitionen.
Rückgabewert: string — <ul class="wk-tree">…</ul>. Leerer
String bei leerem $nodes.
7. Parameter, Optionen und Zustände
$nodes (je Eintrag, rekursiv)
| Schlüssel | Typ | Pflicht | Bedeutung |
|---|---|---|---|
label | string | Ja | Knotentext (hsc()-escaped) |
href | string | Nein | Ziel-URL; fehlt sie, ist der Knoten reiner Gruppierungs-Text (kein Link) |
active | bool | Nein | markiert den aktuellen Knoten (.wk-tree__link–active, fett + Highlight-Hintergrund); an einem Gruppenknoten ohne eigenes open öffnet es zusätzlich dessen <details> (s. open unten) |
open | bool | Nein | seit 2026-07-17, unabhängig von active: erzwingt den Auf-/Zu-Zustand eines Gruppenknotens, ohne die aktive Optik zu setzen. Fehlt der Schlüssel, gilt der Wert von active (100 % rückwärtskompatibel) — nötig, sobald ein Knoten aufgeklappt gezeigt werden soll, OHNE „aktuelle Seite„ zu bedeuten (z. B. ein manuell aufgeklappter, aber nicht besuchter Namespace) |
dataSql | string | Nein | frei nutzbares data-sql-Attribut am Link (Erstkonsument: SQL-Workbench) |
children | array | Nein | verschachtelte Knoten, gleiches Schema — macht den Knoten zu einer <details>/<summary>-Gruppe |
lazy + lazyUrl | bool + string | Nein | seit 2026-07-17: ein Knoten ohne children, aber mit lazy ⇒ true und einer lazyUrl, rendert trotzdem als aufklappbare Gruppe (leeres Platzhalter-<ul>, data-wk-tree-lazy-src). scripts/treeview.js lädt lazyUrl beim ersten Öffnen per fetch() nach und ersetzt den Platzhalter — die Antwort muss selbst wieder ein tree()-Fragment sein. No-JS-Fallback: bleibt ein normaler Link, sofern href gesetzt ist; der tiefere Teilbaum bleibt ohne JavaScript unerreichbar. Nicht mit open ⇒ true kombinieren — siehe Fehlerverhalten unten. |
statusClass | string | Nein | seit 2026-07-17: einzelnes Wort, validiert gegen ^[a-z][a-z0-9-]*$ (ungültige Werte werden stillschweigend verworfen), gerendert als wk-tree__status–<statusClass> auf dem <li>. tree() kennt selbst keine Bedeutung dieser Klasse — der Aufrufer liefert per eigenem CSS die optische Behandlung (Erstkonsument: wkacmenus partial/divert/collection-Namespace-Zustände) |
Erzeugtes Markup
<ul class="wk-tree"> → je Knoten <li>, bei children
verschachtelt als
<details><summary>Label</summary><ul>…</ul></details>.
Fehlerverhalten
| Bedingung | Verhalten |
|---|---|
$nodes leer | Rückgabe leerer String, kein Fehler |
Knoten ohne href und ohne children | rendert als reiner, nicht interaktiver Text — kein Fehler |
active ⇒ true an mehreren Knoten gleichzeitig | keine Prüfung — alle markierten Knoten erhalten die aktive Klasse, der Aufrufer ist für Eindeutigkeit verantwortlich |
statusClass verletzt ^[a-z][a-z0-9-]*$ | wird stillschweigend ignoriert (kein Klassen-Attribut), kein Fehler |
Platzhalter-Knoten (lazy + lazyUrl, keine children) mit open ⇒ true | open wird für diese Kombination ignoriert (Platzhalter rendert immer geschlossen) — s. Abschnitt 9 zur Begründung |
8. Vollständige Beispiele
echo $widgets->tree([ [ 'label' => 'de', 'children' => [ ['label' => 'wiki', 'href' => wl($ID, ['ns' => 'de:wiki']), 'active' => true], ['label' => 'blog', 'href' => wl($ID, ['ns' => 'de:blog'])], ], ], ['label' => 'en', 'href' => wl($ID, ['ns' => 'en'])], ]);
9. Einschränkungen und Randfälle
activemarkiert nur den aktuellen Seitenaufruf; echte Auswahl (Radio/Checkbox je Knoten, Formular-POST) liefert derselection-Modus — Selection / Checkboxes.- Lazy-Load per
lazy/lazyUrl(seit 2026-07-17, s. Abschnitt 7) deckt sehr große Bäume ab, indem tiefere Ebenen erst bei Bedarf nachgeladen werden — ohne diese Option müssen weiterhin allechildrenbeim Aufruf vollständig vorliegen. lazy+open ⇒ trueam selben Knoten: kein Ladevorgang findet statt (das nativetoggle-Ereignis, dasscripts/treeview.jszum Nachladen nutzt, feuert nicht für einen bereits beim Laden offenen Zustand) — der Platzhalter bleibt dauerhaft leer. Kein Anwendungsfall benötigt diese Kombination;tree()rendertopenfür Platzhalter-Knoten daher bewusst nie.- Verschieben von Knoten: Drag & Drop (sichtbare ▲/▼-Links plus optionale Drag-Schicht).
10. Accessibility und Kompatibilität
- Vollständiger Überblick: Accessibility (konsolidiert, nicht hier dupliziert).
- Ohne JavaScript vollständig bedienbar — keine Progressive-Enhancement-Abhängigkeit für die Grundfunktion.
- Browserkompatibilität: abhängig von nativer
<details>-Unterstützung (alle aktuellen Browser; ältere Browser ohne Unterstützung zeigen den Inhalt dauerhaft aufgeklappt statt kollabierbar — degradiert, nicht unbrauchbar).
11. Troubleshooting
Symptom: ein Gruppenknoten öffnet sich nicht automatisch, obwohl ein
Kind-Knoten active ⇒ true trägt.
Ursache: active muss am Gruppenknoten selbst gesetzt sein, damit
dessen <details> offen rendert — die Markierung eines Kind-Knotens
öffnet die Eltern-Gruppe nicht automatisch.
Lösung: active ⇒ true zusätzlich am Eltern-Knoten setzen, wenn die
Gruppe initial offen sein soll.
12. Verwandte Themen
- Demo-Übersicht — alle Kategorien
- FluentUI: Öffentliche Helper-API — vollständiger Methodenvertrag
- SQLite Data Studio — Erstkonsument (Sidebar-Objektbaum)