Issue of dragging a node with a link under it

Hi, I spotted a weird issue regarding dragging a node.

My node template has a transparent container and a text block, which has the selectionObjectName.

I have two swim lanes with the drag-and-drop related event handlers, which simply console logs some message.

I made the fromPort and toPort next to each other on the right-hand side of a node so that the link passed through the Gamma node in swim lane A. Note that there is no link under the Alpha node.

When I dragged the Alpha node, the drag-and-drop event handlers with the two swim lanes were triggered properly.

However when I dragged the Gamma node, nothing was triggered.

I think it is related to the underlying link because if I moved the fromPort to the left-hand side so that the link does not cross the node, dragging the Gamma node fired the drag-and-drop event handlers.

The code below illustrates the issue. Please let me know what I can do to make the swim lane be able to detect the dragging of Gamma when a link passes it. Thank you so much!

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      body {
        margin: 0;
        padding: 0;
        position: absolute;
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
      }
    </style>
  </head>
  <body>
    <div
      id="myDiagramDiv"
      style="border: solid 1px black; width: 100%; height: 70%"
    ></div>
    <script src="../../release/go-debug.js"></script>
    <script src="../../extensions/SwimLaneLayout.js"></script>
    <script>
      function init() {
        const $ = go.GraphObject.make;
        myDiagram = $(go.Diagram, "myDiagramDiv", {
          layout: $(SwimLaneLayout, {
            laneProperty: "group", // needs to know how to assign vertexes/nodes into lanes/groups
            setsPortSpots: false,
            layerSpacing: 20,
            columnSpacing: 50,
            commitLayers: function (layerRects, offset) {
              if (layerRects.length === 0) return;

              var horiz = true;
              var forwards = true;

              var rect = layerRects[forwards ? layerRects.length - 1 : 0];
              var totallength = horiz ? rect.right : rect.bottom;

              for (var i = 0; i < this.laneNames.length; i++) {
                var lane = this.laneNames[i];
                // assume lane names do not conflict with node names
                var group = this.diagram.findNodeForKey(lane);
                if (group === null) {
                  this.diagram.model.addNodeData({ key: lane, isGroup: true });
                  group = this.diagram.findNodeForKey(lane);
                }
                if (horiz) {
                  group.location = new go.Point(
                    -this.layerSpacing / 2,
                    this.lanePositions.get(lane) * this.columnSpacing +
                      offset.y,
                  );
                } else {
                  group.location = new go.Point(
                    this.lanePositions.get(lane) * this.columnSpacing +
                      offset.x,
                    -this.layerSpacing / 2,
                  );
                }
                var ph = group.findObject("PLACEHOLDER"); // won't be a go.Placeholder, but just a regular Shape
                if (ph === null) ph = group;
                if (horiz) {
                  ph.desiredSize = new go.Size(
                    totallength,
                    this.laneBreadths.get(lane) * this.columnSpacing,
                  );
                } else {
                  ph.desiredSize = new go.Size(
                    this.laneBreadths.get(lane) * this.columnSpacing,
                    totallength,
                  );
                }
              }
            },
          }),
        });

        myDiagram.nodeTemplate = $(
          go.Node,
          "Position",
          {
            layerName: "Foreground",
            selectionObjectName: "LABEL",
            minLocation: new go.Point(NaN, -Infinity),
            maxLocation: new go.Point(NaN, Infinity),
          },
          $(go.Shape, "RoundedRectangle", {
            fill: "transparent",
            stroke: null,
            width: 100,
            height: 50,
            portId: "",
            fromLinkable: true,
            toLinkable: true,
            cursor: "pointer",
          }),
          $(
            go.TextBlock, // the text label
            new go.Binding("text", "key"),
            {
              name: "LABEL",
              verticalAlignment: go.Spot.Center,
              textAlign: "center",
              height: 16,
              position: new go.Point(20, 25 - 8),
              background: "white",
            },
          ),
          $(go.Shape, "Rectangle", {
            fill: "blue",
            stroke: null,
            desiredSize: new go.Size(8, 8),
            position: new go.Point(100 - 16, 25 - 4),
            portId: "Left",
            fromSpot: go.Spot.Left,
            toSpot: go.Spot.Left,
            fromLinkable: false,
            toLinkable: false,
            cursor: "pointer",
          }),
          $(go.Shape, "Rectangle", {
            fill: "red",
            stroke: null,
            desiredSize: new go.Size(8, 8),
            position: new go.Point(100 - 8, 25 - 4),
            portId: "Right",
            fromSpot: go.Spot.Right,
            toSpot: go.Spot.Right,
            fromLinkable: true,
            toLinkable: false,
            cursor: "pointer",
          }),
        );

        myDiagram.linkTemplate = $(
          go.Link,
          { routing: go.Link.AvoidsNodes, corner: 10 },
          $(go.Shape, { strokeWidth: 1.5 }),
          $(go.Shape, { toArrow: "Standard", stroke: null }),
        );

        myDiagram.groupTemplate = $(
          go.Group,
          "Horizontal",
          {
            layerName: "Background",
            movable: false,
            copyable: false,
            locationObjectName: "PLACEHOLDER",
            layout: null,
            avoidable: false,
          },
          $(
            go.TextBlock,
            {
              font: "bold 12pt sans-serif",
              angle: 270,
            },
            new go.Binding("text", "key"),
          ),
          $(
            go.Panel,
            "Auto",
            $(
              go.Shape,
              { fill: "transparent", stroke: "orange" },
              {
                mouseDragEnter: (e, thisObj, prevObj) => {
                  console.log(
                    `swim lane: mouseDragEnter ${JSON.stringify(
                      thisObj.part.data,
                    )}`,
                  );
                },
                mouseDragLeave: (e, thisObj, prevObj) => {
                  console.log(
                    `swim lane: mouseDragLeave  ${JSON.stringify(
                      thisObj.part.data,
                    )}`,
                  );
                },
                mouseDrop: (e, thisObj) => {
                  console.log(
                    `swim lane: mouseDrop ${JSON.stringify(thisObj.part.data)}`,
                  );
                },
              },
            ),
            $(go.Shape, {
              name: "PLACEHOLDER",
              fill: null,
              stroke: null,
              strokeWidth: 0,
            }),
          ),
        );

        const nodeDataArray = [
          {
            key: "A",
            isGroup: true,
          },
          {
            key: "B",
            isGroup: true,
          },
          {
            key: "Alpha",
            group: "A",
          },
          {
            key: "Beta",
            group: "B",
          },
          {
            key: "Gamma",
            group: "A",
          },
        ];

        const linkDataArray = [
          {
            from: "Alpha",
            fromPort: "Right",
            to: "Gamma",
            toPort: "Left",
          },
        ];
        const model = new go.GraphLinksModel();
        model.linkFromPortIdProperty = "fromPort";
        model.linkToPortIdProperty = "toPort";
        model.nodeGroupKey = "group";
        model.nodeDataArray = nodeDataArray;
        model.linkDataArray = linkDataArray;
        myDiagram.model = model;
      }

      window.addEventListener("DOMContentLoaded", init);
    </script>
  </body>
</html>

I think what is happening is that you are dragging or dropping the node onto the link.

You could implement drag or drop event handlers on links.

Or you could set Group | GoJS API to true and let the group’s drag or drop event handlers handle those events.

Hi Walter, could you please explain more about the 2nd approach. The swim lane is already a group. Do you mean that I group the Gamma node and the link underneath it into a different group? Do you have an example?

I added handlesDragDropForMembers: true to the swim lane group template. But it does not work.

What I need is when the Gamma label is dropped in a swim lane, the mouseDrop event handler tells me which swim lane the mouse is over when the drop happens. Thank you so much!

For Group.handlesDragDropForMembers to work, as documented you have to set Group.mouseDragEnter, mouseDragLeave, and/or mouseDrop. Not on some GraphObject within the visual tree of the Group as you have done.

Thank you, Walter. I made it work by overriding the draggingTool.