enhancement
major
minor
major
minor
Problem
Moving an object from one place to another is a click sequence in the view layer (com.top_logic.layout.view): select the row, open a dialog or a menu, pick the target. The direct gesture, dragging the row where it belongs, did not exist. Client-side drag and drop was implemented in com.top_logic.layout.react for three things only: file upload, column reordering and the column selector, and dashboard tiles. Rows of TableViewControl were not draggable and a table accepted no drop.
The cases from an issue tracker built on the view layer: a board whose lanes are tables over the tickets of one status category, where moving a ticket to another lane changes its status; planning a ticket into a milestone by dropping it onto the milestone's row; adding a label by dropping the ticket onto the label. All of them are "move objects from a table onto a table or a row of it, then run an action", so the mechanism is one, and it is not a board element.
Solution
Row drag and drop as a declaration on <table>, with the action as an ordinary action chain:
{{{#!xml <-- a lane: accepts tickets dropped anywhere on the table --> <table rows="…" selection="ticket" types="demo.tickets:Ticket">
<drag/>
<drop accept="demo.tickets:Ticket">
<with-transaction>
<execute-script function="tickets -> $tickets.foreach(t -> $t.set(demo.tickets:Ticket#status, demo.tickets:TicketStatus#closed))"/>
</with-transaction>
</drop>
</table>
<-- persons: accepts tickets dropped onto a person's row --> <table rows="…" selection="person">
<drop accept="demo.tickets:Ticket" target="row" target-channel="dropTarget">
<with-transaction>
<execute-script function="tickets -> …">
<inputs><input channel="dropTarget"/></inputs>
</execute-script>
</with-transaction>
</drop>
</table> }}}
- Drag side. <drag type="…"/> makes the rows draggable; the type tag defaults to the table's declared row type, and a table declaring neither is a configuration error. Dragging a selected row drags the whole selection, dragging an unselected row drags that row. The client carries the source control id, the row keys and the type tag; the objects are resolved on the server by the source control (DragSourceControl), so nothing but keys crosses the wire and a drag cannot name an object the session does not display.
- Drop side. <drop accept="types"> declares the accepted types and the target: table (default; an empty lane is a valid target) or row. The accepted tags sent to the client are expanded on the server to the accepted types and all their specializations. The actions run as a ViewActionChain with the list of dropped objects as input, so <with-transaction>, <execute-script>, <open-dialog>, <show-object> and the branches of #29543 all apply. For a row target the row object is written to target-channel before the chain runs (null for a table drop), so scripts read it as a further input. Several <drop> declarations on one table are matched in order, row drops before table drops (TableDropBinding).
- Acceptance. HTML5 dragover needs a synchronous answer, so the client decides the coarse question from the type tag on the payload (carried in a dataTransfer type name, since dragover may not read the data) and the dropAccepts list in the control state, and highlights the table or the row under the pointer (before, onto, after by thirds of the row). The fine question is answered on the server when the drop arrives: the chain runs, or refuses with a message (a <confirm> or <throw-error> in the chain, or a veto of the transaction).
- Control seam. TableViewControl implements DragSourceControl (dragType, dragObjects(keys), dragSelection()) and takes a DropTarget (acceptedTypes, dropOnRows, onDrop(DropEvent)); the drop command carries source id, keys, a selection flag, target key and position (DropPosition), so ReactTreeControl and the composition table can reuse the protocol. A drop is recorded for scripted tests as dropObjects naming the dragged objects and the target row by their model names.
- An editable table (row-edit) declaring <drag> or <drop> is reported as a configuration error; its control is not a TableViewControl.
Documentation and demo
docs/faq/react-view-layer.md, section "Drag and drop of table rows". com.top_logic.demo.react, view "Object list": two lanes Open and Closed over demo.tickets:Ticket, dragging a ticket between them writes its status; a third table of persons accepts a ticket dropped onto a person's row and adds a comment authored by that person. Tests: TestTableViewDragDrop (tl-layout-react), TestTableDropBinding and TestTableElement (tl-layout-view).