Vertical Grid: Grundlagen
Zurück: Demo-Übersicht
1. Übersicht und Zweck
Status: vollständig [Live] — Grundraster, Gruppen/Sektionen,
Readonly-Modus, Validierungszustand und required-Kennzeichnung sind
umgesetzt (Quelle: lib/plugins/wkfluentui/helper/widgets.php, Methode
propertyGrid()).
propertyGrid() stellt ein Objekt als zweispaltiges Label/Wert-Raster
dar — Attribute als Zeilen, nicht als Spalten (Gegenstück zu
dataGrid(), das viele
Objekte als Zeilen zeigt). Zielgruppe: jedes Formular oder jeder
Eigenschaften-Inspektor, der Label+Control-Paare braucht, ohne eine eigene
Formular-Grid-CSS zu bauen.
2. Voraussetzungen
- DokuWiki mit aktiviertem Plugin
wkfluentui. helper_plugin_wkfluentui_widgetsgeladen überplugin_load('helper', 'wkfluentui_widgets').- Controls (
controlHtml) liefert der Aufrufer selbst —propertyGrid()rendert kein einziges Eingabefeld, nur den Rahmen darum (s. Eingabefelder für passende Control-Bausteine).
3. Konzepte
Zwei Rollen, ein Baustein: Formular-Raster (Eingabefelder mit
Label-Spalte) und Eigenschaften-Inspektor (kontextsensitive Anzeige des
ausgewählten Objekts, typisch in der Auxbar) nutzen dieselbe Methode — der
Unterschied liegt nur in $opts['mode'] (Abschnitt 7).
Klartext vs. vertrauenswürdiges HTML: label wird per hsc()
escaped; controlHtml ist bereits vom Aufrufer erzeugtes,
vertrauenswürdiges HTML — dieselbe Konvention wie überall in dieser API
(s. api → Abschnitt „Konzepte").
Rückwärtskompatibilität: propertyGrid($rows) ohne die später
ergänzten Schlüssel (section/value/state/required)
rendert byte-identisch zur ursprünglichen Ein-Parameter-Version.
4. Erste Schritte
/** @var helper_plugin_wkfluentui_widgets $widgets */ $widgets = plugin_load('helper', 'wkfluentui_widgets'); echo $widgets->propertyGrid([ ['label' => 'Name', 'controlHtml' => '<input type="text" name="name" required>'], ['label' => 'Aktiv', 'controlHtml' => '<input type="checkbox" name="aktiv">'], ]);
Falsch: rohen Nutzereingabe-Text direkt als controlHtml übergeben
('controlHtml' => $_POST['x']). controlHtml wird ungeprüft
ausgegeben — der Aufrufer trägt hier die volle XSS-Verantwortung (CWE-79).
5. Verwendung
| Einsatz | Muster |
|---|---|
| Formular im Drawer/Dialog | drawer()/modal() als Hülle, propertyGrid() als Body — Referenz: „Connect to…„-Dialog und Table Designer (wksqliteds, 6 Aufrufstellen in dbadmin/*.php) |
| Eigenschaften-Inspektor in der Auxbar | propertyGrid() mit reinen Anzeige-Zeilen (mode' ⇒ 'display') im .wk-shell-auxbar-Panel; aktualisiert per Click-to-Reload bzw. über das wk-selectionchange-Event des DataGrids (datagrid → Selection-API) |
| Nicht verwenden für | viele gleichartige Objekte (→ dataGrid()) oder lange Fließtext-Dokumentation (→ normale Wiki-Seite) |
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 propertyGrid(array $rows, array $opts = []): string
Zusammenfassung: Rendert ein zweispaltiges Label/Control-Formularraster
(CSS-Grid fit-content(22rem) minmax(0, 1fr)) aus Zeilen-Definitionen,
optional gruppiert in kollabierbare Sektionen und/oder im
Readonly-Anzeigemodus.
Die Beschriftungsspalte darf sich an ihren Inhalt anschmiegen, aber nicht
von ihm bemessen werden. Bis zum 25.08.2026 stand hier max-content 1fr
zusammen mit white-space: nowrap auf der Beschriftung. Wo eine
Beschriftung ein ganzer Satz ist — und in dieser Suite ist sie das bei jeder
Einstellung eines Pakets —, war max-content die Breite dieses Satzes, und
die Steuerelementspalte wurde über den sichtbaren Bereich hinausgeschoben.
Am Bild gemessen, Konfigurationsbildschirm: bei einer Fensterbreite von 768 px lag die rechte Kante des Steuerelements bei 1402 px, bei 1280 px bei 1646 px. Der Inhaltsbereich scrollt nicht seitwärts — die Steuerelemente waren also nicht außerhalb des Bildes, sondern unerreichbar. Bei 390 px war auf dem gesamten Bildschirm kein einziges sichtbar.
fit-content(<länge>) ist max-content mit einer Decke: eine kurze
Beschriftung bestimmt weiterhin ihre eigene Breite, eine lange hört bei der
Decke auf und bricht um. Beides gehört zusammen — ohne
white-space: normal schrumpft eine nicht umbrechende Beschriftung nicht,
sie läuft aus ihrer Zelle heraus, und das Steuerelement ist eine Ebene tiefer
genauso weg.
Rückgabewert: string — vollständiges HTML-Fragment
(<div class="wk-property-grid">…</div>). Leerer String bei leerem
$rows.
7. Parameter, Optionen und Zustände
$rows (je Zeile)
| Schlüssel | Typ | Pflicht | Bedeutung |
|---|---|---|---|
label | string | Nein | Label-Text (hsc()-escaped) |
controlHtml | string | Nein | vertrauenswürdiges HTML des Controls — Aufrufer-escaped |
fullRow | bool | Nein | Zeile spannt über beide Spalten (Hinweistexte, Trenner, breite Controls) |
section | string | Nein | gruppiert aufeinanderfolgende Zeilen als kollabierbare Sektion (Abschnitt „Gruppen/Sektionen“ unten); ein wiederholter Name nach anderen Zeilen beginnt bewusst eine neue Gruppe |
value | string | Nein | nur bei mode => 'display' und ohne controlHtml: hsc()-escapeter Anzeige-Wert |
mono | bool | Nein | Monospace für value |
state | 'error'|'warning' | Nein | 3px-Akzentbalken; reine Anzeige, der Server bleibt Validierungs-Autorität |
stateText | string | Nein | Meldungszeile unter dem Control bei aktivem state |
required | bool | Nein | rotes Sternchen + .a11y-Screenreader-Text am Label — setzt kein required-Attribut im Aufrufer-HTML (s. Fehlerverhalten) |
controlId | string | Nein | ID des Controls im controlHtml: Label erhält for="<controlId>", eine State-Meldungszeile die ID <controlId>__statetext; enthält controlHtml genau einen id="<controlId>"-Treffer ohne eigenes aria-describedby, injiziert das Widget die Verknüpfung — sonst bleibt das Aufrufer-HTML unangetastet und der Aufrufer setzt aria-describedby="<controlId>__statetext" selbst. Ohne controlId: Markup byte-identisch zur Vorversion |
$opts
| Schlüssel | Typ | Bedeutung |
|---|---|---|
mode | 'display' | Readonly-Anzeigemodus, Container-Modifier –display |
persistKey | string | Sektions-Offen-Zustand überlebt Reloads via scripts/propertygrid.js in localStorage unter wk-propertygrid:<persistKey> |
Erzeugtes Markup (Klassen)
| Klasse | Rolle |
|---|---|
.wk-property-grid | Container — CSS-Grid fit-content(22rem) minmax(0, 1fr) |
.wk-property-grid__section-body | Sektionskörper — eigenes Raster mit denselben Spurdefinitionen (display: contents auf der Zeile heißt, dass jedes Raster seine Spuren selbst nennen muss) |
.wk-property-grid--display | Container-Modifier des Readonly-Anzeigemodus |
.wk-property-grid__row | eine Label/Control-Zeile |
.wk-property-grid__row--full | Zeile über beide Spalten (fullRow) |
.wk-property-grid__row--error / …--warning | Zeile mit Validierungszustand |
.wk-property-grid__label | Label-Zelle |
.wk-property-grid__required | rotes Sternchen am Label eines Pflichtfelds |
.wk-property-grid__control | Control-Zelle |
.wk-property-grid__value (--mono) | Anzeige-Wert im Display-Modus |
.wk-property-grid__section | <details open>-Sektionsgruppe |
.wk-property-grid__section-body | Zwei-Spalten-Raster innerhalb einer Sektion |
.wk-property-grid__statetext | Meldungszeile unter dem Control einer State-Zeile |
Gruppen/Sektionen
Zeilen mit gleichem section-Wert werden unter einer Sektions-Kopfzeile
(volle Breite, halbfett mit dünnem Trenner — Optik der Fieldset-Legenden aus
_admin-forms.css) gruppiert; die Kopfzeile ist per Klick/Enter
kollabierbar (<details>-basiert, ohne JavaScript funktionsfähig).
Zeilen ohne section rendern vor der ersten Sektion. Referenz:
DevExpress-VerticalGrid-Kategorien, ADS Table-Designer-Gruppen.
Readonly-Anzeigemodus
propertyGrid($rows, ['mode' => 'display']): Zeilen ohne
controlHtml akzeptieren value (Abschnitt oben); der Container trägt
.wk-property-grid--display (kein Eingabe-Chrome, Werte
selektierbar, Zeilen-Hover über --wk-admin-hover). Das ist der
Standard für den Eigenschaften-Inspektor — Formular-Optik nur, wo
tatsächlich editiert wird.
Fehlerverhalten
| Bedingung | Verhalten |
|---|---|
$rows leer | Rückgabe leerer String, kein Fehler |
required ⇒ true ohne required-Attribut im controlHtml | kein automatischer Fix — das Widget rendert nur die visuelle/a11y-Kennzeichnung; native Browser-Validierung entsteht ausschließlich durch das required-Attribut im Aufrufer-HTML selbst (bewusste Trennung, keine implizite Attribut-Injektion in fremdes HTML) |
value gesetzt, aber mode fehlt | value wird ignoriert, controlHtml bzw. leere Zelle rendert stattdessen |
8. Vollständige Beispiele
Formular mit zwei Sektionen, einer Pflichtfeld-Validierung und Persistenz des Sektions-Zustands:
echo $widgets->propertyGrid([ ['section' => 'Verbindung', 'label' => 'Host', 'controlHtml' => '<input type="text" name="host" required>', 'required' => true], ['section' => 'Verbindung', 'label' => 'Port', 'controlHtml' => '<input type="number" name="port" value="3306">'], ['section' => 'Zugangsdaten', 'label' => 'Benutzer', 'controlHtml' => '<input type="text" name="user">'], ['section' => 'Zugangsdaten', 'label' => 'Passwort', 'controlHtml' => '<input type="password" name="pass">', 'state' => 'warning', 'stateText' => 'Wird verschlüsselt gespeichert.'], ], ['persistKey' => 'connect-dialog']);
9. Einschränkungen und Randfälle
- Kein Nachbau des DevExpress-Mehr-Datensatz-Vergleichsmodus (mehrere Objekte als Spalten) — bei Bedarf eigenständige Initiative; bis dahin: ein
propertyGrid()je Objekt nebeneinander. - Panel-Füll-Verhalten (Grid füllt Drawer/Auxbar, scrollt innen) — Details: Adaptivity.
aria-describedbyzwischenstateTextund Control entsteht nur mit gesetztemcontrolId-Zeilen-Schlüssel (s. Abschnitt 7) — Alt-Aufrufer ohne den Schlüssel rendern unverändert; Details: Accessibility.
10. Accessibility und Kompatibilität
- Vollständiger Überblick: Accessibility (konsolidiert, nicht hier dupliziert).
- Ohne JavaScript bleiben alle Sektionen offen bedienbar (native
<details>-Semantik); nur die Persistenz des Offen-Zustands über Reloads entfällt. - Browserkompatibilität: keine Anforderungen über CSS-Grid-Grundunterstützung hinaus.
11. Troubleshooting
Symptom: Pflichtfeld zeigt kein rotes Sternchen trotz required ⇒ true.
Ursache: required in der Zeilen-Definition und required als
HTML-Attribut im controlHtml sind zwei getrennte Dinge — nur ersteres
steuert das Sternchen.
Lösung: 'required' => true in der Zeile setzen und das
required-Attribut selbst im controlHtml ergänzen, wenn native
Browser-Validierung gewünscht ist.
Symptom: Sektions-Offen-Zustand geht bei jedem Seitenaufruf verloren.
Ursache: $opts['persistKey'] fehlt.
Lösung: einen über Seitenaufrufe stabilen, pro Formular eindeutigen
persistKey setzen.
12. Verwandte Themen
- Demo-Übersicht — alle Kategorien
- FluentUI: Öffentliche Helper-API — vollständiger Methodenvertrag
- FluentUI: Basis-Widget-Katalog — FormLayout-Einordnung (ADS)
- form-fields — Eingabefelder für
controlHtml - Tutorial: Erste Admin-Oberfläche mit FluentUI bauen — Schrittanleitung
- FluentUI: Admin-Bereich (vertieft) — Formularmetrik/Farbsemantik