Transfer custom data from palette to diagram

Let’s say I have the following nodeDataArray in a Palette:

[
    {
        name: "NodeA",
        id: "1",
        description: "Description for Node A",
        source: "exampleSourceA",
    },
    {
        name: "NodeB",
        id: "2",
        description: "Description for Node B",
        source: "exampleSourceB",
    },
    {
        name: "NodeC",
        id: "3",
        description: "Description for Node C",
        source: "exampleSourceC",
    },    
]

and I also have a separate array of data that corresponds with the palette nodes:

[
    {
        name: "CorrespondingDataForNodeA",
        ...different data properties,
    },
    {
        name: "CorrespondingDataForNodeB",
        ...different data properties,
    },
    {
        name: "CorrespondingDataForNodeC",
        ...different data properties,
    },    
]

When I drag one of the nodes from the palette to the diagram, instead of transferring the data stored in the palette nodeDataArray, I want to transfer all of the corresponding data for the specific node while only pulling the name and id properties from the palette node.

How would I go about implementing this? I’ve been looking around at different functions that may need to be overridden but have not found what I’m looking for. Thanks in advance for any help you can provide!

It is customary to define an “ExternalObjectsDropped” DiagramEvent listener on the target Diagram that modifies the collection of dropped Parts as needed. Typically there will just be one Node in the e.subject collection, but the user might be dragging multiple nodes.

Here’s a complete sample. Note how the Palette and the Diagram use different Node templates, where the one in the main Diagram shows the additional properties “prop1” and “prop2”.

<!DOCTYPE html>
<html>
<head>
  <title>Minimal GoJS Editor</title>
  <!-- Copyright 1998-2023 by Northwoods Software Corporation. -->
</head>
<body>
  <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>
    <button id="myLoadButton">Load</button>
    <button id="mySaveButton">Save</button>
  </div>
  <textarea id="mySavedModel" style="width:100%;height:200px">
{ "class": "go.GraphLinksModel",
  "nodeDataArray": [
{"key":1, "text":"hello", "color":"green", "location":"0 0", "prop1":"hallo", "prop2":"bonjour"},
{"key":2, "text":"world", "color":"red", "location":"70 0", "prop1":"wereld", "prop2":"monde"}
  ],
  "linkDataArray": [
{"from":1, "to":2}
  ]}
  </textarea>

  <script src="go.js"></script>
  <script id="code">
const $ = go.GraphObject.make;

// initialize main Diagram
const myDiagram =
  new go.Diagram("myDiagramDiv",
    {
      "ExternalObjectsDropped": e => {
        e.subject.each(node => {
          const extra = ExtraData[node.data.color];
          if (extra) e.diagram.model.assignAllDataProperties(node.data, extra);
        });
      },
      "undoManager.isEnabled": true
    });

const ExtraData = {
  "red": { prop1: "magenta", prop2: "pink" },
  "blue": { prop1: "cyan", prop2: "navy" },
  "green": { prop1: "lime", prop2: "olivedrab" },
  "orange": { prop1: "yellow", prop2: "goldenrod" },
}

myDiagram.nodeTemplate =
  $(go.Node, "Auto",
    { locationSpot: go.Spot.Center },
    new go.Binding("location", "location", go.Point.parse).makeTwoWay(go.Point.stringify),
    $(go.Shape,
      {
        fill: "white", stroke: "gray", strokeWidth: 2,
        portId: "", fromLinkable: true, toLinkable: true,
        fromLinkableDuplicates: true, toLinkableDuplicates: true,
        fromLinkableSelfNode: true, toLinkableSelfNode: true
      },
      new go.Binding("stroke", "color")),
    $(go.Panel, "Vertical",
      { margin: new go.Margin(5, 5, 3, 5) },
      $(go.TextBlock,
        {
          font: "10pt sans-serif",
          minSize: new go.Size(16, 16), maxSize: new go.Size(120, NaN),
          editable: true
        },
        new go.Binding("text").makeTwoWay()),
      $(go.TextBlock,
        new go.Binding("text", "prop1")),
      $(go.TextBlock,
        new go.Binding("text", "prop2"))
    )
  );

// initialize Palette
myPalette =
  new go.Palette("myPaletteDiv",
    {
      nodeTemplate:
        $(go.Node, "Auto",
          { locationSpot: go.Spot.Center },
          $(go.Shape,
            { fill: "white", stroke: "gray", strokeWidth: 2 },
            new go.Binding("stroke", "color")),
          $(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")),
        ),
      model: new go.GraphLinksModel([
          { text: "red node", color: "red" },
          { text: "green node", color: "green" },
          { text: "blue node", color: "blue" },
          { text: "orange node", color: "orange" }
        ])
    });

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

// save a model to and load a model from Json text, displayed below the Diagram
function save() {
  const str = myDiagram.model.toJson();
  document.getElementById("mySavedModel").value = str;
}
document.getElementById("mySaveButton").addEventListener("click", save);

function load() {
  const str = document.getElementById("mySavedModel").value;
  myDiagram.model = go.Model.fromJson(str);
}
document.getElementById("myLoadButton").addEventListener("click", load);

load();
  </script>
</body>
</html>

Thanks for the quick response! Looking at this implementation, it seems close to what I’m looking for except I would need that new data to be applied on drag/when it is hovered over the diagram. Since the palette and diagram have two different node templates, the data for the diagram nodes would need to be copied along with the palette node data once the drag + drop interaction has started.

Looking around further, I think model.copyNodeDataFunction may be what I am looking for, however when testing out overriding this method, the copied dragging node is now not visible, as shown in the gif. (Apologies, it’s pretty slow.)

Below is my test copyNodeDataFunction and all I’m doing between the dragging node working to not working is commenting out this function.

palette.model.copyNodeDataFunction = function(obj, model) {
      console.log(obj, model);
      return obj;
    };

Screen Recording 2023-06-13 at 11.44.04 AM (1)

And now while thinking about it, a potential alternative solution is when I am making the request to retrieve the data for the palette nodes, I can just store the necessary corresponding data in a new property for each node and modify the diagram node template to look for this corresponding data to properly display the node template on the diagram.

I’m still curious on your thoughts/response to my previous reply. Thanks again!

Model.copyNodeDataFunction is a property, not a method, so it cannot be overridden. Well, not that way, at least.

But you could override Model.copyNodeData.

Hmm I’m seeing the same behavior from copyNodeData. Is there an example of overriding copyNodeData to inject some additional data into what’s being copied from the palette to the diagram?

Did you override the method on the source model or on the destination model?

Ahh, I was overriding it on the palette’s model. Overriding it on the destination diagram’s model looks like it’s behaving correctly. I should be able to start implementing inserting the corresponding data.

Thanks!