enhancement
major
minor
defect
critical
major
minor
major
#30132
View layer: value listeners of the model (<value-listeners>) react to field changes in the React form, through a UI-independent listener contract
Problem
In a React view-layer <form>, options, dynamic visibility, constraints and derived (read-only) fields are recomputed when a field they read changes (FormControl.FieldChangeListener, AttributeFieldModel, view-layer article forms). There is no way for a change of one editable field to reset or recompute another editable field of the same edited object: <form> and <field> have no change handler, no model annotation writes one attribute on a change of another, and the classic ValueListener mechanism is not used by the React form. At channel level a <channel init=".." inputs=".."> re-fills a view-owned value on a change of its inputs (article channels), but a <field attribute> cannot be bound to a channel, and channels don't see the form's unsaved edits.
Use case
A dialog creating a project chooses a repository, then a branch and a module of that repository. When the user changes the repository, the branch and module entered for the previous repository are meaningless and should be cleared (or reset to their default). Today the application can only mark the left-over value as invalid at its field.
Proposal
A declarative way to reset a field when the fields it depends on change, e.g. a model annotation on the dependent attribute naming a reset value as TL-Script over the edited object (object -> null or object -> defaultBranch($object)), evaluated by the form when one of the attributes the script reads - or an explicitly named list of attributes - is changed by the user. It writes into the form's overlay like a user edit, so dirty tracking, constraints and save work as usual, and it never fires on the initial load of the form.
Lösung
Statt einer neuen Reset-Annotation wird die vorhandene Modell-Annotation <value-listeners> (TLValueListeners) für beide Oberflächen nutzbar. Sie sitzt am auslösenden Attribut; ein <script>-Listener (ValueListenerByExpression, object -> newValue -> oldValue -> attribute) schreibt beliebige andere Attribute über $object.set(...), z.B. am Attribut repository: p -> new -> old -> { $p.set(`branch, null); $p.set(module, null) }`.
- UI-unabhängiger Vertrag in com.top_logic: AttributeValueListener.valueChanged(TLFormObjectBase object, TLStructuredTypePart attribute, Object oldValue, Object newValue). Er ist der Elementtyp von TLValueListeners.getListeners(); Tag-Namen bleiben, bestehende Modelle bleiben gültig. TLFormObjectBase ist die gemeinsame Schnittstelle des klassischen TLFormObject und desReact-TLObjectOverlay.
- ValueListenerByExpression implementiert nur noch diesen Vertrag (kein FormField mehr).
- Klassische Oberfläche: DefaultAttributeFormFactory hängt die Listener über AttributeValueListenerAdapter (com.top_logic.element) an das Feld; Verhalten unverändert.
- React-Oberfläche: ValueListenerDispatch ruft die Listener des geänderten Attributs bei einer Benutzeränderung und bei einer Kaskade auf, nicht beim Laden, beim Verwerfen, beim Speichern oder für Werte, die ein Dialog übernimmt. Je Attribut laufen die Listener höchstens einmal pro Kette (Schutz gegen Zyklen). Das gilt für das Formularobjekt und für die Zeilen einer <composition-table>.
- Overlay-Änderungen werden sichtbar: Ein Schreiben in das TLObjectOverlay (tUpdate/`revert`) aktualisiert das Feld des Attributs und meldet die Änderung wie eine Benutzeränderung: Dirty-Status, Constraints, Optionen, Sichtbarkeit und abgeleitete Felder folgen.
- Neue Zeilen einer `<composition-table>' sind ebenfalls Overlays (über dem neuen transienten Objekt) wie die bestehenden Zeilen; Anlegen, Bearbeiten, Entfernen und Speichern verhalten sich unverändert.
- Beschrieben im Entwicklerartikel view-layer/forms ("Reacting to a field change"); Beispiel in der React-Demo (Demos → Constraint Test: Kategorie leert den Code, Name einer Position leert ihren Preis).
Migration
Eine eigene Java-Klasse, die com.top_logic.layout.form.ValueListener implementiert und in einer Modell-Annotation <value-listeners> konfiguriert ist, muss stattdessen com.top_logic.model.annotate.AttributeValueListener implementieren: valueChanged(TLFormObjectBase object, TLStructuredTypePart attribute, Object oldValue, Object newValue) erhält das bearbeitete Objekt (Formular-Overlay) und das Attribut statt des FormField. Modelldateien und <script>-Listener müssen nicht geändert werden.