enhancement
major
minor
major
minor
major
#30019
TABLE.debounce() verwendet globalen Singleton-Timer — Slice-Nachladen schlägt bei mehreren Tabellen fehl
Zusammenfassung
Die Funktion TABLE.debounce() in table.js speichert den Timer-Handle auf dem übergebenen Function-Object method._tId. Da für handleTableScroll immer dasselbe Function-Object verwendet wird, existiert nur ein einziger Timer für alle Tabellen-Instanzen. Wenn mehrere Tabellen innerhalb von 50ms programmatisch gescrollt werden (z.B. durch eine gemeinsame Selektion über einen Kanal), cancelt jede Tabelle den Timer der vorherigen. Nur die letzte Tabelle lädt ihre Zeilen nach — die übrigen bleiben mit leeren Spacer-Rows stehen.
Betroffene Datei
table.js — Funktion debounce (Zeile 1792 in TL 7.10.3)
Reproduktion
- Mehrere Tabellen mit Lazy-Row-Loading sind gleichzeitig sichtbar (oder über denselben Selektionskanal verbunden)
- Eine externe Aktion (Kanal-Selektion) löst in allen Tabellen gleichzeitig einen Selektionswechsel aus
- Der Server sendet für jede Tabelle ein TABLE.updateScrollPosition() JSSnipplet
- Jede Tabelle setzt scrollTop → Browser feuert scroll-Events → debounce(handleTableScroll, 50, tableContainer)
- Jeder debounce-Aufruf löscht den Timer des vorherigen clearTimeout(method._tId)
- Nach 50ms feuert nur der letzte Timer → nur eine Tabelle lädt Zeilen nach
Die Reproduktion ist nicht immer deterministisch, da sie von der Reihenfolge und dem Timing der Browser-Scroll-Events abhängt.
Manuelles Scrollen danach funktioniert, weil ein isoliertes Scroll-Event keinen Timer-Konflikt verursacht.
Ursache
{{{ // table.js:1792 (TL 7.10.3) debounce: function(method, delay, tableContainer, args) {
clearTimeout(method._tId); // ← löscht Timer ALLER Tabellen
method._tId = setTimeout(function() { // ← überschreibt mit neuem Timer
method(tableContainer, args);
}, delay);
}, }}}
method._tId ist ein Property auf dem Function-Object TABLE.handleTableScroll. Da dies ein Singleton ist, teilen sich alle Tabellen denselben Timer-Slot.
Vorgeschlagener Fix
Timer-Handle pro Tabelle auf dem tableContainer-DOM-Element speichern statt global auf dem Function-Object:
{{{ debounce: function(method, delay, tableContainer, args) {
var timerKey = '_debounce_' + method.name;
clearTimeout(tableContainer[timerKey]);
tableContainer[timerKey] = setTimeout(function() {
delete tableContainer[timerKey];
method(tableContainer, args);
}, delay);
}, }}}
Erläuterung
- method.name liefert den Funktionsnamen (z.B. "handleTableScroll") — damit sind verschiedene debounced Methoden auf demselben Container unterscheidbar
- Der Timer-Key wird nach Ausführung der verzögerten Funktion vom Container entfernt (delete)
- Alle existierenden Aufrufer von debounce übergeben bereits tableContainer — keine Anpassung der Aufrufer nötig:
- addListenerToReloadRowsOnScroll (Zeile 864): scroll-Event-Listener
- addObserverToUpdateTableOnResize (Zeile 780): ResizeObserver
Kein Einfluss auf `initTable()`
Der Voll-Initialisierungspfad initTable → renderRows → Callback → updateScrollPosition ist nicht betroffen, da dort die Zeilen vorab über renderRows() mit clientDisplayData geladen werden, bevor gescrollt wird. Das Nachladen hängt dort nicht vom scroll-Event ab.
Komponente
tl-core — script/tl/table.js