Unique names on drag/drop

Hi,

I want to create unique names by appending a count of similar nodes to the component name. This is the binding function:

new go.Binding('text', '', (data) => {
                            let sameObjArray = [];
                            this.diagram.model.nodeDataArray.forEach(element => {
                                if (element['text'] == data.text && data.text == data.componentName) {
                                    sameObjArray.push(element);
                                }
                            });
                            let count = sameObjArray.length;
                            if (count == 0) {
                                return data.componentName;
                            } else {
                                while (_.find(sameObjArray, { 'componentName': data.componentName + Math.abs(count) }) != undefined) {
                                    count++;
                                }
                                return data.componentName + Math.abs(count);
                            }
                        }),

This is the scenario for which it is not working:

  • I drag node “Comp1” on to diagram.
  • Drag the next node which is then “Comp2”
  • Next I delete the node “Comp1”
  • When i drag the next node, i expect it to be “Comp3”.
    But, for some reason, it does show the name as “Comp3”, but the model data still has the name “Comp2”

I cant really understand what is going on. Could you tell me how to resolve this?

If you have deleted the first node, then when you create the third one there are only two such nodes in the model.

I really believe that you do not want to use data binding in this manner. That Binding will be re-evaluated frequently, and you do not want the visible name to be changing at all once it’s been set.

It would be better to keep a separate counter for each component type and use that counter to set a data property that is never changed. You could keep those counters on the Model.modelData object, so that the information is not lost, even if the user deletes all nodes. You could implement a ModelChanged listener to notice whenever a node data object is inserted into the Model.nodeDataArray.

Could you please give us an example for reference?

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
  <script src="https://unpkg.com/gojs"></script>
  <script>
    function init() {
      var $ = go.GraphObject.make;

      myDiagram =
        $(go.Diagram, "myDiagramDiv",
          {
            "undoManager.isEnabled": true,
            // don't start updating componentName counters until after the model has been loaded
            "InitialLayoutCompleted": function(e) { e.diagram.addModelChangedListener(onNodeDataAdded); }
          });

      myDiagram.nodeTemplate =
        $(go.Node, "Auto",
          new go.Binding("location", "location", go.Point.parse).makeTwoWay(go.Point.stringify),
          $(go.Shape,
            {
              fill: "white", stroke: "gray", strokeWidth: 4,
              portId: "", cursor: "pointer",
              fromLinkable: true, toLinkable: true,
              fromLinkableDuplicates: true, toLinkableDuplicates: true,
              fromLinkableSelfNode: true, toLinkableSelfNode: true
            },
            // assume componentName is also a color, so no converter needed
            new go.Binding("stroke", "componentName")),
          $(go.TextBlock,
            {
              margin: new go.Margin(5, 5, 3, 5), font: "10pt sans-serif",
              minSize: new go.Size(16, 16), maxSize: new go.Size(120, NaN)
            },
            new go.Binding("text"))
        );

      myPalette =
        $(go.Palette, "myPaletteDiv",
          {
            nodeTemplateMap: myDiagram.nodeTemplateMap,
            model: new go.GraphLinksModel([
              { text: "red node", componentName: "red" },
              { text: "green node", componentName: "green" },
              { text: "blue node", componentName: "blue" },
              { text: "orange node", componentName: "orange" }
            ])
          });

      myOverview =
        $(go.Overview, "myOverviewDiv",
          {
            observed: myDiagram,
            contentAlignment: go.Spot.Center
          });

      load();
    }

    // This is called on each Model Changed event after the model has been loaded.
    // Whenever a node data object has been added, increment the counter corresponding
    // to "componentName" and set the node data's "text" property to the desired string.
    // The counters are held in the shared Model.modelData object, which will remember
    // the componentName counts even if there are no nodes.
    // Changes to the counters are recorded in the UndoManager, so an undo will revert
    // the counts along with removing added nodes.
    function onNodeDataAdded(e) {
      if (e.change === go.ChangedEvent.Insert && e.propertyName === "nodeDataArray" &&
          !e.model.skipsUndoManager) {  // skip any temporary additions, such as during drag-and-drop
        var data = e.newValue;
        var cname = data.componentName;
        if (!cname) cname = "none";
        var counters = e.model.modelData;
        var count = counters[cname] || 0;
        count++;
        e.model.set(counters, cname, count);
        e.model.set(data, "text", cname + " " + count);
      }
    }

    // save a model to and load a model from Json text, displayed below the Diagram
    function save() {
      var str = myDiagram.model.toJson();
      document.getElementById("mySavedModel").value = str;
    }
    function load() {
      // disable onNodeDataAdded while the model is being loaded
      myDiagram.removeModelChangedListener(onNodeDataAdded);
      var str = document.getElementById("mySavedModel").value;
      myDiagram.model = go.Model.fromJson(str);
    }
  </script>
</head>
<body onload="init()">
  <div style="width: 100%; display: flex; justify-content: space-between">
    <div style="display: flex; flex-direction: column; margin: 0 2px 0 0">
      <div id="myPaletteDiv" style="flex-grow: 1; width: 100px; background-color: floralwhite; border: solid 1px black"></div>
      <div id="myOverviewDiv" style="margin: 2px 0 0 0; width: 100px; height: 100px; background-color: whitesmoke; border: solid 1px black"></div>
    </div>
    <div id="myDiagramDiv" style="flex-grow: 1; height: 400px; border: solid 1px black"></div>
  </div>
  <div id="buttons">
    <button id="loadModel" onclick="load()">Load</button>
    <button id="saveModel" onclick="save()">Save</button>
  </div>
  <textarea id="mySavedModel" style="width:100%;height:200px">
  { "class": "GraphLinksModel",
    "modelData": {"red":1, "green":1, "blue":0, "orange":0, "none":0},
    "nodeDataArray": [ 
  {"key":1, "text":"green 1", "componentName":"green", "location":"0 0"},
  {"key":2, "text":"red 1", "componentName":"red", "location":"90 0"}
   ],
    "linkDataArray": [ {"from":1, "to":2} ]}
  </textarea>
</body>
</html>

I followed the same example, and the function onNodeDataAdded(e) is being called multiple times while drag/drop causing the count to increment incorrectly.

What might be the issue?

Are you checking Model.skipsUndoManager as my code does, above?

yes. This is the exact function:

onNodeDataAdded(e) {
    if (e.change === go.ChangedEvent.Insert && e.propertyName === "nodeDataArray" &&
        !e.model.skipsUndoManager) {
        let component = e.newValue['text'];
        let compCount = e.model.modelData[component];
        compCount++;
        e.model.set(e.model.modelData, component, compCount);
        e.model.set(e.newValue, 'componentName', e.newValue['componentName']+compCount);
    }
}

And the listener:

 this.diagram.addDiagramListener('InitialLayoutCompleted', (e) => {
        e.diagram.addModelChangedListener(this.onNodeDataAdded);
    });

If I put an alert in that method:

    function onNodeDataAdded(e) {
      if (e.change === go.ChangedEvent.Insert && e.propertyName === "nodeDataArray" &&
          !e.model.skipsUndoManager) {  // skip any temporary additions, such as during drag-and-drop
alert("adding node")
        var data = e.newValue;
        . . .

I find that the alert only happens once during a completed drag-and-drop from the Palette to the main Diagram. And it doesn’t happen at all if the drag-and-drop is cancelled.