enhancement
major
minor
major
minor
major
#29554
React view <table>: Spalten für beliebige Zeilenobjekte – berechnete Spalten, eingebettete Spalten referenzierter Objekte, dynamische Spalten; transiente Zeilen
Affects 8.0.0-SNAPSHOT (2026-09-11), tl-layout-view.
Found while building an aggregation view (one row per month, with count and sum) in the Expenses application. Three limits together make the <table> element unusable for computed rows:
1. Transient rows crash the view
RowSourceObserver.registerObjectListeners() calls key(object) → object.tHandle() for every TLObject row, unconditionally and regardless of observed-types. TransientObject.tHandle() throws UnsupportedOperationException, so a <rows> expression that yields new(`m:T`, transient: true) objects fails to attach the view (TableElement:898 → RowSourceObserver:130/211/261). <object-list> uses the same observer (ObjectListElement:295).
2. Map rows render empty
ColumnProviderService.attributeValue(row, attr) returns null unless row instanceof TLObject, so a <rows> expression returning a list of maps gives a table with the right number of rows and every cell empty. No error, no hint.
3. A column names a flat attribute only
TableElement.ColumnConfig has attribute, filter, readonly, sort — no value expression and no a.b navigation. A per-row computed value (count of related objects, a sum, a label of a related object) therefore needs a derived attribute on the row type, which is impossible when the row type belongs to the engine (tl.accounts:Person as the row of a "per employee" overview).
group-by exists and groups, but ColumnProviderService never sets .aggregate(...), so group cells are CellContent.empty() — no sums either.
Lösung
Die Spaltenkonfiguration <columns> des React-<table>-Elements (und der <composition-table>) ist eine Liste von Spaltendeklarationen. Jede Deklaration ist ein eigenes Tag und liefert null bis n Spalten für den Zeilentyp der Tabelle:
- <column attribute="x"/> – unverändert: Beschriftung, Typ, Anzeige, Filter, Sortierung und Editor werden aus dem Modellattribut abgeleitet. Neu ist eine optionale <label>-Überschreibung und ein optionales <aggregate> (siehe unten). Bestehende Views bleiben gültig.
- <computed-column name="…" type="tl.core:Double" [multiple] [label] [readonly] [sort] [width]> mit <value>row -> …</value> – berechnete Spalte über beliebigen Zeilenobjekten (Maps, transiente Objekte, Engine-Typen). Der Zelltyp wird explizit angegeben; Filter, Sortierung, Breite, Suchtext und Anzeige werden wie bei einem Attribut aus dem Typ abgeleitet (ohne type: Anzeige über die Bezeichnung). Mit <update>row -> value -> …</update> und optional <can-update>row -> …</can-update> ist die Spalte im Bearbeitungsmodus (row-edit) editierbar; der Editor ist das typbasierte Formularfeld. <inputs><input channel="…"/></inputs> übergibt Kanalwerte als führende Argumente aller Funktionen.
- <embedded-columns reference="assignee"> bzw. reference="account.contact" (Referenzpfad, schrittweise gegen den Zeilentyp aufgelöst) – bettet Spalten des referenzierten Objekts ein. Der Inhalt sind normale Spaltendeklarationen jeder Art, die gegen den Zieltyp aufgelöst werden; ohne Inhalt werden die main-properties des Zieltyps (sonst alle nicht versteckten Attribute) angezeigt. Die übrigen Attribute des Zieltyps werden in der Spaltenauswahl angeboten (zunächst ausgeblendet). Spaltennamen sind Pfade (assignee.name), Beschriftungen werden mit den Bezeichnungen der Referenzen präfixiert ("Bearbeiter / Name"); ein <label> an der Einbettung ersetzt das Präfix. Ein mehrwertiger Schritt liefert die gesammelten Werte. Variante mit berechnetem Zielobjekt: <embedded-columns name="…" type="m:Contact" [multiple]><label>…</label><object>row -> …</object>. Eingebettete Spalten sind schreibgeschützt.
- <dynamic-columns name="…" [type|<column-type>] [multiple] [width] [readonly]> mit <columns>…</columns>, optional <column-name>, <column-label>, <value>row -> col -> …</value>, <update>row -> col -> value -> …</update>, <can-update>, <aggregate>rows -> col -> …</aggregate> – eine Spalte je Element der berechneten Menge, mit den Argumentkonventionen des DynamicColumnProviderByExpression der klassischen Oberfläche. Spaltennamen sind <name>.<column-name>; die Spalten sind die, die die Funktion beim Aufbau der Tabelle liefert.
- Aggregate in Gruppenzeilen: <aggregate>rows -> …</aggregate> an <column> und <computed-column> berechnet aus den Zeilen einer Gruppe (group-by) den Wert, der in der Gruppenkopfzeile über die Typanzeige der Spalte dargestellt wird.
- Standardspalten: Eine <table> ohne <columns> zeigt die main-properties ihres Zeilentyps (sonst alle nicht versteckten Attribute); die übrigen Attribute der konfigurierten types werden in der Spaltenauswahl angeboten.
Technisch: ColumnSetup trägt eine Typbeschreibung (ColumnType: Typ, Multiplizität, Annotationsquelle, optional das Attribut), einen Wertzugriff, eine optionale Bearbeitungsstrategie (CellEditing) und eine optionale Aggregatfunktion; ColumnProviderService leitet Spaltenart, Breite, Filter, Suchtext und Anzeige aus der Typbeschreibung ab und benutzt den übergebenen Wertzugriff. FieldControlService zeigt Werte auch typbasiert ohne Attribut an. RowSetTableControl liest Zellen über den Spaltenzugriff und bearbeitet sie über die Bearbeitungsstrategie der Spalte (AttributeCellEditing für Attribute, ValueCellEditing mit FunctionFieldModel für berechnete Spalten). RowSourceObserver registriert für transiente Zeilen (tTransient()) keine Listener.
Nebenbei behoben: Die Testquellen von tl-layout-react kompilierten auf master nicht (die Tests aus #29561 riefen den in #29552 geänderten Konstruktor von DefaultReactContext auf).
Demo: Seite "Tabellenspalten" in tl-demo-react.