major
#29776
TL Views: Einheitliches Drag & Drop für Tabellen, Baum-Tabellen, Bäume und Kanban-Boards mit mehreren Drop-Operationen, Drop-Positionen und Drag-Arten
Kontext
In der React-Oberfläche (.view.xml-Schicht, com.top_logic.layout.view / com.top_logic.layout.react) ließen sich Objekte bisher nur zwischen Tabellen und auf Kanban-Boards ziehen. Bäume unterstützten Drag & Drop nicht, Baum-Tabellen ließen sich in einer View nicht konfigurieren, und eine Tabelle mit mehreren Drop-Operationen konnte nicht unterscheiden, ob ein Objekt zwischen zwei Zeilen eingefügt oder auf eine Zeile fallen gelassen wird.
Drag & Drop ist jetzt für alle listen- und baumartigen Komponenten der React-Oberfläche einheitlich – Tabelle, Baum-Tabelle, Baum und Kanban-Board:
- Auf einem Ziel können mehrere Drop-Operationen konfiguriert werden, die sich nach der Art der gezogenen Daten und nach der Drop-Position unterscheiden.
- Während des Ziehens sieht der Anwender genau die Markierung (Einfügelinie zwischen Zeilen/Knoten oder hervorgehobene Zeile/Knoten) der Operation, die beim Loslassen ausgeführt wird, bzw. den Grund, warum nicht abgelegt werden kann.
- Objekte lassen sich zwischen allen diesen Komponenten in beide Richtungen ziehen.
Die klassische Oberfläche bleibt unverändert. Das entsprechende Problem dort ist in #29515 beschrieben.
Konfiguration
Drag-Art
- <drag kind="…">: optionaler, frei gewählter Name für die Art des Drags (z. B. ticket). Ohne Angabe hat der Drag keine Art.
- <drop accept="k1, k2">: optionale Liste von Arten. Leer nimmt jeden Drag an (mit oder ohne Art), sonst nur Drags einer der genannten Arten. Der Client prüft die Art bereits beim Überfahren.
- Einschränkungen nach Modelltyp der gezogenen Objekte werden mit refuse-if formuliert.
Drop-Art (target)
| Element | target | Bedeutung | refuse-if | Kanäle |
| <table> | table | auf die Tabelle als Ganzes | objects -> target -> reason (target = null) | target-channel |
| <table> | row | auf eine Zeile | objects -> target -> reason | target-channel |
| <table> | ordered | zwischen die Zeilen (Einfügelinie); neben den Zeilen = am Ende | objects -> before -> reason | before-channel |
| <tree> | tree / node / ordered | wie oben; ordered = zwischen Geschwister oder als Kind | objects -> parent -> before -> reason (ordered) | parent-channel, before-channel |
| <tree-table> | table / row / ordered | wie beim Baum | wie beim Baum | wie beim Baum |
Die gezogenen Objekte sind immer das erste Argument; ein Skript mit weniger Parametern ignoriert die übrigen (objects -> reason genügt, wenn die Referenzen nicht gebraucht werden). before ist das Objekt, vor dem eingefügt wird (null = am Ende bzw. als letztes Kind), parent der Elternknoten. Falsch platzierte Optionen (z. B. target-channel bei ordered, parent-channel in einer flachen Tabelle, target-executability außerhalb von row/`node`) sind Konfigurationsfehler.
Auswahl der Operation
Der Client teilt eine Zeile bzw. einen Knoten je nach den angebotenen Drop-Arten in Zonen: flache Tabelle nur geordnet → Hälften, nur „auf Zeile“ → ganze Zeile, beides → Drittel; Baum und Baum-Tabelle mit geordnetem Drop → Drittel (oberhalb / innerhalb / unterhalb). Der Server geht die Drop-Operationen in der deklarierten Reihenfolge durch (Art → Position für die Drop-Art der Operation → Ausführbarkeit / refuse-if); die erste, die annimmt, wird angezeigt und beim Ablegen ausgeführt. Baum-Zonen: oberhalb von X → (Eltern von X, X); innerhalb von X → (X, erstes Kind); unterhalb von X → (Eltern von X, nächstes Geschwister) bzw. bei aufgeklapptem X → (X, erstes Kind).
Baum-Tabelle
Neues Element <tree-table>: Wurzel und Kinder-Funktion wie bei <tree> (root, children, parents, observed-types), Spalten, Auswahl, Filter, Presets und Personalisierung wie bei <table>, Drag & Drop wie oben.
Technische Umsetzung
- com.top_logic.layout.react.control.dnd: DragSourceControl#dragKind() / isDragEnabled(), DropTarget#acceptedKinds() (AcceptedKinds) und #dropModes(), DropMode (CONTROL/ONTO/ORDERED), DropZone, DropLocation (Control, Onto, Insert(parent, before)), DropRequest, DropVerdict.accepted(location), DropMarker, DropPlace, TreeDropPlace über DropTreeNavigation (gemeinsame Baum-Regeln für Baum und Baum-Tabelle). DropSupport bündelt Probe, Drop, Aufzeichnung und Wiedergabe (aufgezeichnet wird die gewählte Drop-Art mit ihren Referenzobjekten). DropPosition entfällt.
- ReactTreeControl / TLTreeView: auf die gemeinsame Schicht umgestellt; das bisherige eigene Protokoll und die Abhängigkeit von TreeDropTarget entfallen.
- TableViewControl im Baum-Modus: Baum-Zonen und -Positionen über RowHierarchy (TreeRowSource).
- View-Schicht: DropReference / DropSignature legen pro Drop-Art die Referenzobjekte (Kanäle und refuse-if-Argumente nach den gezogenen Objekten) fest. AbstractTableElement (gemeinsame Basis von <table> und <tree-table>), TreeStructureConfig / TreeFunctions (gemeinsam für <tree> und <tree-table>).
- Dokumentation: view-layer/tables.md (Tabelle und Baum), view-layer/tree-tables.md.
Prüfung
- Demo com.top_logic.demo.react: Demos → Drag and Drop (dnd-demo.view.xml, Modell demo.dnd) mit Notiz-Palette, Liste, Baum, Baum-Tabelle und Kanban-Board; Knoten werden zwischen allen Komponenten verschoben, Notizen auf Knoten abgelegt, das Board setzt die Spalte; Verschieben eines Knotens in sich selbst wird mit Begründung abgelehnt.
- Tests: TestTableViewDragDrop, TestTableViewTreeDragDrop, TestReactTreeControlDragDrop, TestDropBinding, TestTableElementDragDrop, TestTreeElementDragDrop, TestTreeTableElement, TestKanbanBoardDragDrop.
Migration
- <drag type="modul:Typ"> gibt es nicht mehr: durch <drag kind="…"> mit einem frei gewählten Namen ersetzen.
- <drop accept="…"> nennt keine Modelltypen mehr, sondern Drag-Arten: accept="modul:Typ" durch die Art der passenden <drag kind> ersetzen (oder accept weglassen, um jeden Drag anzunehmen). Eine Einschränkung auf einen Modelltyp (bisher inkl. Untertypen) wird in refuse-if formuliert, z. B. objects -> !$objects.filter(o -> !$o.instanceOf(`modul:Typ)).isEmpty()`.
- refuse-if erhält die gezogenen Objekte als erstes Argument: target -> objects -> … wird zu objects -> target -> ….
- Mehrere <drop> an einem Element werden in deklarierter Reihenfolge geprüft (bisher: Zeilen-Drops vor Tabellen-Drops). Bei gemischten Drops ggf. die Reihenfolge anpassen.
- Java: DropTarget#dropOnRows() → dropModes(), acceptedTypes() → acceptedKinds(), check(DropEvent) → check(DropRequest), DragSourceControl#dragType() → dragKind()/`isDragEnabled(), `DropEvent trägt eine DropLocation statt Ziel und DropPosition.