We have a GoJS scenario where a user drags a node from the Palette and drops it onto a link connecting two existing nodes.
Our custom logic then:
Hides the dragged node.
Creates new in/out links.
Inserts the new node between the existing connected nodes, effectively splitting the original link into two new links.
However, GoJS automatically wraps the drag-and-drop action in one transaction, and our custom logic (that updates links) runs in a separate transaction.
As a result, users need to press Undo twice to revert to the previous state — once for the GoJS drag-drop, and once for our custom logic.
We’d like to provide a smoother user experience where only one Undo reverts the entire operation (both the drop and link adjustments).
Question:
What’s the best way to merge or handle these transactions in GoJS so that the drag-and-drop plus our link-insertion logic is treated as a single undoable action?
If I understand you correctly, your first step is to hide the dropped node. That implies the new node still exists in both in the diagram and in the model.
Instead, why not cancel that (successful) drag-and-drop action? That would avoid having the first transaction be recorded at all. Something like:
new go.Link({
. . .,
mouseDrop: (e, link) => {
const newnode = e.diagram.selection.first(); // this assumes a single node
// start your asynchronous process of splicing the new node(s) into this link
. . .
// cancel the DraggingTool's operation
e.diagram.currentTool.doCancel();
}
})
. . .
Hi @walter I tried the approach you suggested. It works correctly when I drag a node that’s already on the main diagram and drop it onto a link — the node gets inserted between the connected nodes as expected.
However, when I drag a node from another diagram (the palette) and drop it onto a link in the main diagram, it doesn’t work. If I call doCancel(), it stops the process entirely, and no node is inserted between the link.
I want to clarify — does doCancel() stop just the current operation, or does it cancel the entire transaction?
Our goal is: when a node is dragged from the palette and dropped onto a link in another diagram, the newly added node should be inserted between the existing link’s connected nodes.
Tool.doCancel stops the Tool’s operation, including canceling any transaction that it might be conducting.
In your case, when dragging from another diagram/palette, does a node get added to the target diagram at all? Do you call your splice-node-into-existing-link code from a Link.mouseDrop event handler’s asynchronous code?
Yes, Walter. In our case, the new node we’re adding should be placed onto the link. However, with the above suggestion, that’s not happening at all. Also, our mouseDrop on the link is asynchronous.
GraphObject.mouseDrop is a synchronous event handler – if present it is called by the DraggingTool on a mouse-up event, within the transaction. I think we agree that in your app that event handler will want to invoke something to cause an asynchronous transaction that actually splices a new Node (and Link) into where a Link had been.
If I add the following event handler to my Links, then when a drag-and-drop happens to drop a node onto that link, the drag-and-drop is cancelled.
For within-diagram drag-and-drops, the node(s) go back to where they had started from. For palette-to-diagram drag-and-drops, there’s no new node, as if nothing had happened.
On the mouseDrop event, we are calling the following dispatch:
dispatch(“GO_DIAGRAM#DROP_ON_NODE_OR_LINK_REQUESTED”, {
event: e,
});
In above dispatch we are doing on commit:
diagram.model.commit(model => {
// Update the new node with a unique key
model.setDataProperty(newNode, ‘key’, uniqueKey);
});
Using this dispatch, we perform the necessary operations to insert a new node between two existing nodes on a link.
If I add e.diagram.currentTool.doCancel(); in the mouseDrop event—when dropping a node from the palette onto a link in another canvas/diagram—the behavior remains the same.
How can we handle this entire operation as a single transaction?
I used e.diagram.currentTool.doCancel(); inside the mouseDrop event. However, when dragging and dropping a node from the palette onto the canvas, the node does not get added to the diagram.
Interestingly, the same logic works as expected when dropping an orphan node between existing nodes on a link within the same canvas.
In both cases, because you want to perform the work asynchronously, you need to pass the new node information and the existing link information to your database/model code for splicing a new node into that link. A successful response event handler can do the single transaction that I described above, How to manage single undo/redo for drag-and-drop with custom link handling in GoJS? - #8 by walter