Replace node on mouseDrop

Dear Support:

(I’m referring to the Planogram’s usage of mouseDrop as a starting point).

I’d like offer a special case of dragging shapes from the palette wherein if you drag a palette shape onto an existing diagram node, it “replaces” that node. All that means in our case is to change the value of a field in the data section of the part on the diagram.

Here’s my code:

    // helper definitions for node templates

    nodeStyle() {
        return [
            {
                mouseDragEnter: function(e, node) {
                    e.handled = true;
                    node.isSelected = true;
                },
                mouseDragLeave: function(e, node) {
                    node.isSelected = false;
                },
                mouseDrop: function(e, node) {
                    let droppedPart = node.diagram.selection.first().part;
                    if (droppedPart.data._isPaletteNode) {
                        let existingPart = node.part;
                        node.diagram.model.startTransaction("perform drop-in replacement");
                        node.diagram.model.setDataProperty(existingPart.data, 'class', droppedPart.data.class);
                        node.diagram.model.commitTransaction("perform drop-in replacement");
                    }
                    node.diagram.currentTool.doCancel();
                },

                //width: 100,
                resizable: true,
                locationSpot: go.Spot.Center,
                fromSpot: go.Spot.AllSides, toSpot: go.Spot.AllSides,
                fromLinkable: true, toLinkable: true,
                fromLinkableDuplicates: true, toLinkableDuplicates: true

            },
            // TwoWay Binding of the desiredSize
            new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify),
            // The Node.location comes from the "loc" property of the node data,
            // converted by the Point.parse static method.
            // If the Node.location is changed, it updates the "loc" property of the node data,
            // converting back using the Point.stringify static method.
            new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify)
        ];
    },

I’m using “doCancel” at the end because I don’t want the normal “ExternalObjectsDropped” event handler to run. This is simply because in this case I don’t want a new node, I just want to use information from the node being dropped to update the existing node.

Unfortunately this isn’t working perfectly (I think the doCancel is messing things up). This approach seems awkward and there must be a better way.

I tried setting e.handled = true in mouseDrop, but that did no prevent the ExternalObjectsDropped event handler from being called.

Thank you

Cancelling a drop causes the transaction to be rolled-back, thereby losing any side-effects.

Instead, allow it to complete normally, and call node.diagram.commandHandler.deleteSelection().

Thank you Walter, removing the newly dropped object immediately in onExternalObjectsDropped works fine. The only remaining issue is that the change I make to the existing part in the model during the mouseDrop does not seem to be on the undo history (i.e. I can’t undo the change). Here is my mouseDrop code:

               mouseDrop: function(e, node) {
                    let droppedPart = node.diagram.selection.first().part;
                    if (droppedPart.data._isPaletteNode) {
                        droppedPart.data._deleteOnDrop = true;
                        let existingPart = node.part;
                        node.diagram.model.startTransaction("perform drop-in replacement");
                        node.diagram.model.setDataProperty(existingPart.data, 'class', droppedPart.data.class);
                        node.diagram.model.commitTransaction("perform drop-in replacement");
                    }
                },

And the check in the ExternalObjectsDropped code:

        myDiagram.addDiagramListener("ExternalObjectsDropped", (diagramEvent) => {
            let work = [];
            diagramEvent.subject.each((e) => { work.push(e); });

            for (let e of work) {
                if (e.data._deleteOnDrop) {
                    e.diagram.commandHandler.deleteSelection();
                    continue;
                }

Thank you

If you read the description for the GraphObject.mouseDrop event handler, GraphObject | GoJS API, you will see that it is documented to happen within a transaction that is being conducted by the DraggingTool. So you do not need to perform your own transaction. However, that is not the problem.

But I just tried this on my node template, and it worked well, supporting undo/redo after repeated drops on the same node:

        {
          mouseDrop: function(e, node) {
            var val = (node.data.count) ? node.data.count + 1 : 1;
            e.diagram.model.setDataProperty(node.data, "count", val);
          }
        },

Also, the Diagram was initialized by:

      $(go.Diagram, "myDiagramDiv",
        {
          nodeTemplate: nodeTemplate,
          allowDrop: true,
          initialContentAlignment: go.Spot.Center,
          "ExternalObjectsDropped": function(e) {
            e.diagram.commandHandler.deleteSelection();
          },
          "undoManager.isEnabled": true
        });

Thank you Walter,

I looked further and you’re right, the undo is happening, however the node’s colorization (set by the NodeTemplateMap is not).

I am using the “class” field as my NodeCategoryProperty (here is the JSON of the diagram):

{
    "class": "go.GraphLinksModel",
    "dataFormat": "flow-1",
    "nodeKeyProperty": "id",
    "nodeCategoryProperty": "class",
    "linkFromPortIdProperty": "fromPort",
    "linkToPortIdProperty": "toPort",
    "nodeDataArray": [
        {
            "class": "Flow.Eval",
            "loc": "-53.546875 -73.5",
            "id": "9d1fc190-4e73-4a54-9a18-9fb3889edfe4",
            "options": {
                "Script": [
                    "() => {",
                    "    return '';",
                    "}"
                ]
            },
            "name": "abc"
        }
    ],
    "linkDataArray": []
}

And I am changing the class property of the object:

                mouseDrop: function(e, node) {
                    let droppedPart = node.diagram.selection.first().part;
                    if (droppedPart.data._isPaletteNode) {
                        droppedPart.data._deleteOnDrop = true;
                        let existingPart = node.part;
                        node.diagram.model.setDataProperty(existingPart.data, 'class', droppedPart.data.class);
                    }
                },

If I undo and redo far back enough in the history, it does change the rendering, but only after some non-“class” data change occurs to the node.

OK, I just modified my test app so that there are two template categories, and updated the models (both for the diagram and for the palette) so that Model.nodeCategoryProperty was set to “class”, rather than the default which is “category”. Here’s the model in JSON:

{ "class": "go.GraphLinksModel",
  "nodeCategoryProperty":"class",
  "nodeDataArray": [ 
{"key":1, "text":"hello", "class":"blue", "location":"0 0"},
{"key":2, "text":"world", "class":"orange", "location":"70 0"}
  ],
  "linkDataArray": [
{"from":1, "to":2} 
  ]}

I changed my GraphObject.mouseDrop event handler in both templates to be:

          mouseDrop: function(e, node) {
            var cat = e.diagram.selection.first().category;
            e.diagram.model.setDataProperty(node.data, "class", cat);
          }

The ExternalObjectsDropped DiagramEvent listener remained the same:

          "ExternalObjectsDropped": function(e) {
            e.diagram.commandHandler.deleteSelection();
          },

Everything worked as I think you would expect, both when dropping and upon undo or redo.