enhancement
major
minor
major
minor
major
#29671
TL Views: Drag & Drop in Tabellen nach Regeln einschränken, mit Rückmeldung pro Ziel während des Ziehens
Problem
A <table>'s <drop> runs an action chain that modifies persistent state (in the TL Development app, dropping a ticket on a board lane changes its status; dropping it on a milestone row plans it). Commands of the same page are restricted declaratively, with <authenticated-only/>, <access-control>, <visible-if> or <edit-executability>. A drop cannot be: TableElement.DropConfig offers only accept, target, target-channel and actions, and DragConfig only type. The config types that hold executability rules are ViewCommand and FormElement.
Every session that can see the rows can therefore drop onto them and run the chain. The only way to refuse would be a check inside the chain's script, which is the non-declarative security the view layer is meant to avoid. Found while auditing anonymous access, snapshot build 42.
Expected
<drop> (and, for the source side, <drag>) accept <executability> rules like a command does. When a rule refuses:
- the table does not announce the row as draggable, or as a drop target;
- a drop request sent anyway is refused on the server with the rule's reason.
Lösung
Drag & Drop in Tabellen der View-Schicht wird deklarativ eingeschränkt - tabellenweit, pro Zeile und pro gezogenem Objekt. Der Benutzer sieht schon während des Ziehens, ob das Ziel unter dem Mauszeiger die gezogenen Objekte annimmt, und warum nicht.
Konfiguration
<drag>
- executability (mit input und observed-types wie bei einem Kommando): tabellenweite Regeln, live ausgewertet. Solange sie ablehnen, sind die Zeilen nicht ziehbar.
- row-executability: Regeln pro Zeile, die Zeile ist die Eingabe der Regel. Eine abgelehnte Zeile ist nicht ziehbar; ein Drag einer Selektion, die eine solche Zeile enthält, wird als Ganzes abgelehnt.
<drop>
- executability (mit input und observed-types): tabellenweite Regeln, live ausgewertet. Ein abgelehnter Drop wird dem Client nicht angekündigt und nicht angewendet.
- target-executability: Regeln pro Zielzeile, die Zielzeile ist die Eingabe (nur für target="row"; bei einem Drop auf die Tabelle ist das ein Konfigurationsfehler).
- refuse-if: TL-Script-Funktion target -> objects -> reason über Zielzeile und gezogene Objekte, mit derselben Ergebnisauswertung wie <disabled-if>: kein Wert oder false nimmt an, true lehnt mit allgemeiner Begründung ab, ein Text bzw. i18n-Literal lehnt mit dieser Begründung ab.
Beispiel: {{{#!xml <drop accept="demo.tickets:Ticket" target="row" target-channel="dropPerson">
<executability>
<authenticated-only/>
</executability>
<refuse-if>person -> tickets -> ...</refuse-if>
...
</drop> }}}
Rückmeldung während des Ziehens
Der Client merkt sich den laufenden Drag dokumentweit. Betritt der Zeiger ein neues Ziel (Zeile + Position oder die Tabelle), sendet die Tabelle ein technisches Kommando dropProbe (nicht aufgezeichnet, ohne Seiteneffekt). Der Server löst die gezogenen Objekte auf und fragt DropTarget.check(DropEvent) nach einem DropVerdict (angenommen / abgelehnt mit Begründung); das Urteil kommt als Control-State zurück und gilt für die Dauer des Drags. Ein abgelehntes Ziel zeigt den Nicht-Ablegen-Cursor, eine Markierung und die Begründung als Hinweis.
Beim Drop prüft der Server dasselbe Urteil erneut. Ein abgelehnter Drop läuft nicht, er wird als Warnung mit der Begründung beantwortet (wie ein von seiner Regel abgelehntes Kommando, #29679).
Technisch
- com.top_logic.layout.react: DropVerdict, DropTarget.check, DropProbeArguments, DragSourceControl.isDraggable; TableViewControl.setDragSource(type, predicate), refreshDragSource(), refreshDropTarget().
- com.top_logic.layout.view: LiveExecutability (Live-Neuauswertung von Regeln über Eingabekanal, beobachtete Typen und ObservableRule) und das Konfigurations-Mixin ExecutabilityConfig (input, executability, observed-types), gemeinsam genutzt von ViewCommandModel/`ViewCommand.Config` und <drag>/`<drop>`.
- Demo: tickets.view.xml in com.top_logic.demo.react.