LinkingTool customization

Hi, I am customizing the LinkingTool in order to addLinkData programmatically. I simply implemented the insertLink method of the LinkingTool. However, when I tried to add a link between two nodes, the code worked, but I always got the following error in the chrome console.

Screen Shot 2022-10-10 at 10.24.37 AM

I got the same error even tried to select the newly added link programmatically by

          myDiagram.model.addLinkData(linkData);
		  const newLink = myDiagram.findLinkForData(linkData);
		  newLink.isSelected = true;

Do I have to override the doMouseUp method? Am I doing the correct way? Do you have any sample code that illustrates how to correctly customize the LinkingTool and RelinkingTool?

The complete HTML + JS code is below. Thanks so much for your time!

<!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>
  </head>
  <body>
    <div
      id="myDiagramDiv"
      style="border: solid 1px black; width: 100%; height: 700px"
    ></div>
    <script src="../../release/go-debug.js"></script>
    <script>
      function init() {
        const $ = go.GraphObject.make;

        const myDiagram = $(go.Diagram, "myDiagramDiv", {
          layout: $(go.LayeredDigraphLayout, {
            layeringOption: go.LayeredDigraphLayout.LayerLongestPathSource,
            layerSpacing: 100,
          }),
        });

        myDiagram.nodeTemplate = $(
          go.Node,
          "Spot",
          $(go.Shape, "RoundedRectangle", {
            fill: "white",
            width: 100,
            height: 30,
            portId: "",
            fromLinkable: true,
            toLinkable: true,
          }),
          $(
            go.TextBlock, // the text label
            new go.Binding("text", "key"),
            {
              verticalAlignment: go.Spot.Center,
              textAlign: "center",
            },
          ),
          $(go.Shape, "Rectangle", {
            fill: null,
            stroke: null,
            desiredSize: new go.Size(8, 8),
            alignment: new go.Spot(0, 0.5, 4, 0),
            portId: "Left",
            fromSpot: go.Spot.Left,
            toSpot: go.Spot.Left,
            fromLinkable: false,
            toLinkable: true,
          }),
          $(go.Shape, "Rectangle", {
            fill: null,
            stroke: null,
            desiredSize: new go.Size(8, 8),
            alignment: new go.Spot(1, 0.5, -4, 0),
            portId: "Right",
            fromSpot: go.Spot.Right,
            toSpot: go.Spot.Right,
            fromLinkable: true,
            toLinkable: false,
          }),
        );

        myDiagram.linkTemplate = $(
          go.Link, // the whole link panel
          {
            selectable: true,
          },
          { relinkableFrom: true, relinkableTo: true },
          $(
            go.Shape, // the link shape
            { strokeWidth: 1.5 },
          ),
          $(
            go.Shape, // the arrowhead
            { toArrow: "Standard", stroke: null },
          ),
        );

        const nodeDataArray = [
          {
            key: "Alpha",
          },
          {
            key: "Beta",
          },
          {
            key: "Gemma",
          },
        ];

        const model = new go.GraphLinksModel();
        model.nodeDataArray = nodeDataArray;
        myDiagram.model = model;

        const ltool = myDiagram.toolManager.linkingTool;
        ltool.isForwards = true;

        ltool.insertLink = (fromnode, fromport, tonode, toport) => {
          console.log(fromnode, fromport, tonode, toport);
		  const linkData = {
            from: fromnode.data.key,
            to: tonode.data.key,
            fromPort: "Right",
            toPort: "Left",
          };
          myDiagram.model.addLinkData(linkData);
		  const newLink = myDiagram.findLinkForData(linkData);
		  newLink.isSelected = true;
        };

		const rtool = myDiagram.toolManager.relinkingTool;
		rtool.isForwards = true;

		rtool.reconnectLink = (existinglink, newnode, newport, toend) => {
			console.log(existinglink, newnode, newport, toend);
			myDiagram.model.removeLinkData(existinglink.data);
			let linkData;
			if (toend) 
				linkData = {
					from: existinglink.data.from,
					fromPort: existinglink.data.fromPort,
					to: newnode.data.key,
					toPort: newport.portId
				};
			else
				linkData = {
					from: newnode.data.key,
					fromPort: newport.portId,
					to: existinglink.data.to,
					toPort: existinglink.data.toPort
				};
			myDiagram.model.addLinkData(linkData);
		}
      }

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

For most situations, you don’t need to override any method. Just set LinkingTool | GoJS API or implement a “LinkDrawn” DiagramEvent listener.

Hi Walter, thanks for your quick response. LinkDrawn listener is triggered after the new link data has been added to the diagram model. But it is not what we want.

What we want is to customize the addition of the new link data instead of using the default GoJS implementation. For example, before a link is added, I want to check with the backend business logic whether the link to be added is valid or not.

The only way I could think of is to override the LinkingTool.

In your “LinkDrawn” DiagramEvent listener you could ask the server whether the link is OK or not. If it isn’t you could maybe show a message and execute a transaction to remove the link.

If you need to dynamically customize the data that is added, rather than just copy some custom data, you could override the LinkingTool.insertLink method by modifying the LinkingTool.archetypeLinkData object and then calling the super method.

Hi Walter, in order to follow the pattern of your data flow, we cannot add a link and then remove it if it is invalid. We should not add it at the first place if it is invalid.

Is it the correct way to override insertLink? Why does it throw an error in the browser console? Do you have any GoJS sample that customizes the LinkingTool?

const ltool = myDiagram.toolManager.linkingTool;
        ltool.isForwards = true;

        ltool.insertLink = (fromnode, fromport, tonode, toport) => {
          console.log(fromnode, fromport, tonode, toport);
		  const linkData = {
            from: fromnode.data.key,
            to: tonode.data.key,
            fromPort: "Right",
            toPort: "Left",
          };
          myDiagram.model.addLinkData(linkData);
        };

Calling GraphLinksModel.addLinkData adds a Link to the Diagram.

So if you don’t want to do that, you need to override insertLink not to add that link data object and thus create that Link, but instead ask the server about creating a “link” in the server’s model. Then when it succeeds you can execute a transaction to actually call GraphLinksModel.addLinkData.

The problem with what you are suggesting is that when the user thinks she has drawn a new Link, they won’t see the Link and will think something’s wrong. That’s why I think it’s better UX to actually create a Link that is visible but appears temporary or transient or proposed or whatever. Then when the response from the server comes, you can either change the link to be a permanent one or delete it.