Drag and Drop a TreeLayout ReactPalette Node onto ReactDiagram Node

Hello there! We had a perfectly functioning ReactPalette and ReactDiagram configuration but had to make a UX change and the ReactPalette has now changed to a TreeLayout.

Prior to changing the ReactPalette layout we had a function handling when a Palette Node is dragged and dropped on a currently SELECTED existing Diagram Node.

const nodeDropped = async (ev: any, Node: any, go: any) => {
const docletType = Node?.qb;
const part = Node?.diagram?.selection?.first()?.part?.qb;
const documentSection: DiagramDocumentSection = JSON.parse(
JSON.stringify(part)
);
};

The issue now is that part is undefined… what’s happening here?

Thank you so much!

So the only real change is that the Palette.layout is now a TreeLayout instead of the standard GridLayout?

Layouts should have no effect on dragging behavior. So I cannot explain the change in behavior.

What is qb?

If that’s a minified property name, you’ll need to replace it with the documented property. Minified names are consistent between releases, so if your GoJS version changed, that may be causing your issue.

Walter that’s correct, the only change is from default layout to TreeLayout.

The Diagram no longer returns a first() selection… it’s Null.

const docletDropped = async (ev: any, Node: any, go: any) => {
    const part = Node?.diagram?.selection?.first();
};

Here’s the new Palette config:

const palette = $(go.Palette, {
			'animationManager.isEnabled': false,
			'commandHandler.copiesParentKey': false,
			'commandHandler.copiesTree': false,
			'commandHandler.deletesTree': false,
			allowCopy: false,
			allowDelete: false,
			allowMove: true,
			allowTextEdit: false,
			contentAlignment: go.Spot.TopLeft,
			initialContentAlignment: go.Spot.TopLeft,
			isReadOnly: false,
			maxSelectionCount: 1,
			padding: new go.Margin(5, 0, 0, 4),
			layout: $(go.TreeLayout, {
				alignment: go.TreeLayout.AlignmentStart,
				angle: 0,
				compaction: go.TreeLayout.CompactionNone,
				layerSpacing: 16,
				layerSpacingParentOverlap: 1,
				nodeIndentPastParent: 1.0,
				nodeSpacing: 0,
				setsPortSpot: false,
				setsChildPortSpot: false,
				arrangementSpacing: new go.Size(0, 0),
			}),
		});

With that initialization of your palette, there’s really no point in using the Palette class – just use a Diagram class and set Diagram.allowDragOut to true.
You won’t need to set CommandHandler.copiesTree or deletesTree, nor Diagram.allowMove since those are the default values for any Diagram or Palette.

Where and how is your “nodeDropped” function being used? In which diagram is the node selected or not? When dealing with a drag-and-drop that starts in one diagram and goes into another, it might not be clear which diagram is which.

I changed the Palette configuration as you suggested but still same behavior.

When I check the Node.diagram.selection.count prop it’s 0 (zero).

My function handler is being passed in through this template config:

const diagramDocletType = $(
		go.Node,
		'Auto',
		{
			mouseDrop: (ev: any, node: any) => {
				docletDropped(ev, node, go);
			},
		}
	);

Here’s my new Palette config:

const palette = $(go.Diagram, {
			'animationManager.isEnabled': false,
			'commandHandler.copiesParentKey': false,
			allowCopy: false,
			allowDelete: false,
			allowDragOut: true,
			allowTextEdit: false,
			contentAlignment: go.Spot.TopLeft,
			initialContentAlignment: go.Spot.TopLeft,
			isReadOnly: false,
			maxSelectionCount: 1,
			padding: new go.Margin(5, 0, 0, 4),
			layout: $(go.TreeLayout, {
				alignment: go.TreeLayout.AlignmentStart,
				angle: 0,
				compaction: go.TreeLayout.CompactionNone,
				layerSpacing: 16,
				layerSpacingParentOverlap: 1,
				nodeIndentPastParent: 1.0,
				nodeSpacing: 0,
				setsPortSpot: false,
				setsChildPortSpot: false,
				arrangementSpacing: new go.Size(0, 0),
			}),
		});

The UX is as follows:

  1. Select Diagram Node
  2. Drag and drop Node from Palette into selected Diagram Node

The problem was that you have set Diagram.allowCopy to false, which prevents a drag-and-drop or copy-and-paste from copying nodes, whether inside the diagram or to another diagram.

Here’s my sample that uses your code, along with my own model data and node template that adds your mouseDrop event handler. I have added some commentary.

<!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: 120px; background-color: floralwhite; border: solid 1px black"></div>
      <div id="myOverviewDiv" style="margin: 2px 0 0 0; width: 120px; 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"},
{"key":2, "text":"world", "color":"red", "location":"70 0"}
  ],
  "linkDataArray": [
{"from":1, "to":2}
  ]}
  </textarea>

  <script src="https://unpkg.com/gojs"></script>
  <script id="code">
const $ = go.GraphObject.make;

// initialize main Diagram
const myDiagram =
  new go.Diagram("myDiagramDiv",
    {
      "undoManager.isEnabled": true
    });

// the use of "Node" here is a bit confusing, because it is not referring to the go.Node class
const docletDropped = async (ev, Node, go) => {  // async is not needed, and probably is not desired
    const part = Node?.diagram?.selection?.first();
    alert(part);
};

myDiagram.nodeTemplate =
  $(go.Node, "Auto",
    { mouseDrop: (ev, node) => docletDropped(ev, node, go) },
    { 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,
      },
      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),
        editable: true
      },
      new go.Binding("text").makeTwoWay())
  );

// initialize Palette
myPalette =
  new go.Diagram("myPaletteDiv",
    {
      allowDragOut: true, // needed
			'animationManager.isEnabled': false,
			'commandHandler.copiesParentKey': false,
			//'commandHandler.copiesTree': false,  // this is the default value
			//'commandHandler.deletesTree': false,  // this is the default value
			allowCopy: true,  // must be true to be able to copy nodes out!!!
      allowInsert: false,  // maybe you want this too?
			allowDelete: false,
			//allowMove: true,  // this is the default value
			allowTextEdit: false,
			contentAlignment: go.Spot.TopLeft,
			initialContentAlignment: go.Spot.TopLeft,
			//isReadOnly: false,  // this is the default value
      maxSelectionCount: 1,
			padding: new go.Margin(5, 0, 0, 4),
			layout: $(go.TreeLayout, {
				alignment: go.TreeLayout.AlignmentStart,
				angle: 0,
				compaction: go.TreeLayout.CompactionNone,
				layerSpacing: 16,
				layerSpacingParentOverlap: 1,
				nodeIndentPastParent: 1.0,
				nodeSpacing: 0,
				setsPortSpot: false,
				setsChildPortSpot: false,
				arrangementSpacing: new go.Size(0, 0),
			}),
      nodeTemplateMap: myDiagram.nodeTemplateMap,
      model: new go.GraphLinksModel([
        { key: 1, text: "red node", color: "red" },
        { key: 2, text: "green node", color: "green" },
        { key: 3, text: "blue node", color: "blue" },
        { key: 4, text: "orange node", color: "orange" }
      ], [
        { from: 1, to: 2 },
        { from: 1, to: 3 },
        { from: 1, to: 4 },
      ])
    });

// 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>

Yessss! thank you so much :-)