DataGrid: Data Paging and Scrolling
Zurück: Demo-Übersicht
1. Übersicht und Zweck
Status: [Live] — natives Scrollen, serverseitiges Paging
(paging-Opt) und darauf aufsetzendes Infinite Loading für Grids, die
über die Viewport-Höhe hinausreichen; bewusst kein virtuelles Scrollen
(Architekturentscheidung, Abschnitt 3). Referenz: DevExpress ASPxGridView
„Paging and Scrolling„ / Virtual-Scroll-Modus.
2. Voraussetzungen
Keine zusätzlichen — der dataGrid()-Basiskontrakt genügt; natives Scrollen ist ohne weitere Konfiguration aktiv.
3. Konzepte
Bewusste Grenze: kein virtuelles Scrollen in der ersten Ausbaustufe. Begründung: virtuelles Scrollen (nur die sichtbaren Zeilen im DOM halten, Rest per Scroll-Offset nachrendern) erfordert eine JS-Rendering-Engine mit eigenem Zustand — das widerspricht dem Grundsatz „kein Nachbau einer JS-Grid-Engine“ (layout → Layout-Mechanik). Für sehr große Ergebnismengen ist serverseitige Reduktion (Filter, Paging) der vorgesehene Weg, nicht Client-Virtualisierung.
4. Erste Schritte
// Ohne Paging-Feature liegt die Verantwortung für eine sinnvolle Zeilenzahl // beim Aufrufer -- z.B. über eine SQL-LIMIT-Klausel vor dataGrid(). $rows = $db->queryAll('SELECT id, name, status FROM aufgaben ORDER BY id LIMIT 500'); echo $widgets->dataGrid($columns, $rows, [ 'empty' => 'Keine Aufgaben gefunden.', ]);
Falsch: ein Ergebnis mit mehreren Zehntausend Zeilen ungefiltert an
dataGrid() übergeben. Ohne virtuelles Scrollen rendert das Widget jede
Zeile als echtes <tr> — das DOM wird groß und die Seite spürbar
langsam; das ist kein Bug des Widgets, sondern die dokumentierte Grenze
dieser Ausbaustufe.
5. Verwendung
| Datenmenge | Empfohlener Weg |
|---|---|
| klein/mittel (bis niedrige Hunderte Zeilen) | dataGrid() direkt, natives Scrollen genügt |
| groß | SQL-LIMIT/WHERE vor dem Aufruf, s. Abschnitt 4 |
| sehr groß, nutzergesteuert | serverseitiges Paging über $opts['paging'] (Abschnitt 6) |
6. API-Referenz
[Live] Natives Browser-Scrollen
Der Grid-Body (.wk-datagrid__body) trägt overflow: auto — alle
$rows werden vollständig ins DOM gerendert, der Browser übernimmt das
Scrollen (Rezept-Zutat 3 in
Adaptivity →
CSS-Rezept). Das ist die einzige unterstützte Darstellungsart — es gibt
keinen $opts-Schlüssel für Virtualisierung; Paging ist seit dem
Vollausbau über $opts['paging'] verfügbar (nächster Abschnitt).
[Live] Optionales Paging
Für serverseitig paginierte Ergebnismengen (Virtualisierung bleibt ausgeschlossen, s. Abschnitt 3 — nur klassisches Seiten-Paging):
'paging' => ['page' => int, 'pageSize' => int, 'total' => int, 'pageUrl' => callable]rendert eine Paginierungsleiste unter dem Grid-Body (.wk-datagrid__paging): Erste/Zurück/„Seite X von Y (N Einträge)„/Weiter/Letzte als normale Links mitaria-label; inaktive Enden rendern als ausgegraute Spans.- Analog zu
sortUrl/filterAction:pageUrl(int $page): stringliefert die URL je Seite — der No-JS-Weg ist das Feature; der Aufrufer paginiert$rowsselbst (SQLLIMIT/OFFSET). pageSizeist optional; ohne Angabe gilt die projektweite Standardseitengrößehelper_plugin_wkfluentui_widgets::PAGE_SIZE= 12 Zeilen. Jede Liste im Produkt beginnt bei derselben Seitengröße — wer hier eine andere Zahl setzt, weicht bewusst ab und sollte den Grund im Aufrufer nennen.pageUrlliefert eine ROHE URL (Trenner&, nicht&). Das Widget maskiert selbst, an jeder Stelle, an der es die URL einbettet. Wird die bereits maskierte Form übergeben, entsteht&im Markup; der Browser dekodiert das zu einem literalen&, und jeder Parameter nach dem ersten heißt dannamp;do,amp;page, … — die Seite 2 landet still in der Admin-Übersicht. Betroffen waren am 2026-07-30 vier Aufrufer gleichzeitig (wkidentity Benutzer, wkblog Beiträge/Kommentare, wksqliteds Browse), weil dieurl()-Hilfsmethoden dieser Module standardmäßig maskieren. Das Widget normalisiert seit dieser Runde zusätzlich (str_replace('&', '&', …)), weilwl()Parameterwerte prozentkodiert und ein literales&in einer erzeugten URL deshalb immer der Trenner ist, niemals Daten.
[Live] Infinite Loading (Scroll-Nachladen)
Regel (Nutzeranforderung 2026-07-29): überschreitet ein Grid die
Viewport-Höhe, wechselt es automatisch in den Infinite-Loading-Modus.
Umsetzung: paging veröffentlicht neben dem Pager zusätzlich die URL der
Folgeseite und die eigenen Summen als Datenattribute auf
.wk-datagrid__more; scripts/gridinfinite.js entscheidet zur
Laufzeit, ob aktiviert wird — die entscheidende Tatsache (reicht dieses
Grid über den sichtbaren Bereich hinaus?) existiert erst nach dem Layout.
| Verhalten | Beschreibung |
|---|---|
| Aktivierungskriterium | Der scrollende Grid-Body scrollt tatsächlich (scrollHeight > clientHeight) oder die Nachlade-Zeile liegt unter dem Viewport-Rand. Kurze Listen bleiben beim Pager. |
| Beobachter | IntersectionObserver mit rootMargin: 300px und Root = der scrollende Vorfahr (in der Workbench-Shell scrollt der Grid-Body selbst; ein viewport-verankerter Beobachter würde dort nie auslösen). |
| Nach der Aktivierung | Der Pager wird ausgeblendet und durch eine echte Schaltfläche „Mehr laden“ ersetzt — Scroll-Auslösung allein ist ohne Zeigegerät nicht bedienbar. Statuszeile role=status/aria-live=polite: „24 von 40 geladen„. |
| Letzte Seite | Schaltfläche verschwindet, Pager bleibt ausgeblendet: alles, wohin er navigieren könnte, steht bereits im DOM. |
| Fehlerfall | Ein fehlgeschlagener Abruf (Sitzung abgelaufen, Serverfehler, falsch maskierte URL) stoppt den Modus, stellt den Pager wieder her und schreibt die Ursache in die Browserkonsole. Der Leser darf nie ohne beides zurückbleiben. |
| Abschalten | 'paging' => [..., 'infinite' => 'off'] hält ein Grid strikt bei Seiten — sinnvoll für Bildschirme, deren Zeilen an Ort und Stelle bearbeitet werden: nachgeladene Seiten würden ein ungespeichertes Formular still vergrößern. |
| Nachträglich eingefügte Grids | window.wkGridInfinite.init(root) initialisiert einen Teilbaum idempotent — für Grids, die erst per dialog.js/auxbar.js in die Seite geholt werden. |
Kein virtuelles Scrollen (Abschnitt 3 gilt unverändert): jede geladene
Zeile bleibt eine echte Zeile im DOM. Auswahl, Strg+A, Sortierung, die
Zählung im Massenaktions-Balken und der CSV-Export behalten damit ihre
einfache Bedeutung „alles Geladene“; Zeilen werden nie recycelt oder
entfernt. Ohne JavaScript ändert sich nichts — der Pager ist die
Navigation.
7. Parameter, Optionen und Zustände
| Schlüssel | Typ | Bedeutung |
|---|---|---|
$opts['paging']['page'] | int | aktuelle Seite |
$opts['paging']['pageSize'] | int | Zeilen je Seite |
$opts['paging']['total'] | int | Gesamtzahl Zeilen (für „Seite X von Y„) |
$opts['paging']['pageUrl'] | callable | rohe URL je Seite (Trenner &); No-JS-Weg und Quelle der Nachlade-URL |
$opts['paging']['infinite'] | auto|off | Scroll-Nachladen; Standard auto |
Kombination mit Sorting/Filtering (verbindliche Aufrufer-Regel): bei
serverseitigem Paging müssen sortUrl-Callables und filterHidden
die aktuelle Seite in der erzeugten URL bzw. den Hidden-Feldern mitführen —
sonst springt ein Sortier- oder Filterwechsel implizit auf Seite 1, ohne
dass das für den Nutzer sichtbar ist.
8. Vollständige Beispiele
$page = max(1, (int)($_GET['p'] ?? 1)); $rows = $db->queryAll('SELECT id, name FROM aufgaben ORDER BY id LIMIT 50 OFFSET ' . (($page - 1) * 50)); echo $widgets->dataGrid($columns, $rows, [ 'paging' => [ 'page' => $page, 'pageSize' => 50, 'total' => $gesamt, 'pageUrl' => static fn (int $p) => wl($ID, ['p' => $p]), ], ]);
9. Einschränkungen und Randfälle
- Kein virtuelles Scrollen (bewusste, dauerhafte Grenze, s. Abschnitt 3) — Infinite Loading ist keine Virtualisierung, sondern Anfügen echter Zeilen.
- Infinite Loading hängt an
paging. Ein Grid ohnepagingrendert alle übergebenen Zeilen und scrollt nativ; es gibt dort nichts nachzuladen (so arbeitet z.B. das Ergebnis-Grid der SQL-Workbench, das seine Zeilen in einem Zug erhält). IntersectionObserverundfetchsind Voraussetzung; fehlt eines, bleibt der Pager sichtbar und alles funktioniert wie vorher.- Die praktische Obergrenze für „noch flüssig“ hängt vom Browser/Gerät ab und ist hier bewusst nicht als feste Zahl behauptet (unbelegte Performance-Aussage).
10. Accessibility und Kompatibilität
- Natives Scrollen ist vollständig tastaturbedienbar (Bild-hoch/-runter, Pfeiltasten bei Fokus im Grid-Body) ohne zusätzlichen Code.
- Keine Browserkompatibilitäts-Anforderungen über CSS-
overflow-Grundunterstützung hinaus.
11. Troubleshooting
Symptom: Grid wird bei großen Ergebnismengen spürbar langsam.
Ursache: alle Zeilen werden vollständig ins DOM gerendert — das ist die dokumentierte Grenze dieser Ausbaustufe, kein Bug.
Lösung: Ergebnismenge vor dem Aufruf reduzieren (SQL-LIMIT/
WHERE) oder serverseitiges Paging über $opts['paging']
aktivieren (Abschnitt 6).
12. Verwandte Themen
- Demo-Übersicht — alle Kategorien
- DataGrid: Adaptivity — CSS-Rezept, das das Scroll-Verhalten trägt
- DataGrid: Filtering — serverseitige Datenreduktion (Filter)
- area:layout → Layout-Mechanik — Grundsatz „kein Nachbau einer JS-Grid-Engine„