How to set a "placeholder" of a draggable part to a cursor programmatically

Hello, when you drags a node from palette onto the diagram, meanwhile user drags & moves mouse around on the diagram there is a “placeholder” that moves together with cursor

2023-02-20_10-29-24

I would like to know how I can control/set this “placeholder”

Thanks
Vlad.

What the user is dragging around in the target diagram is a copy of what they will get when that node data is added. Your video doesn’t show what happens when the drop occurs, but I assume the new node will look like what they were dragging around.

Or am I misunderstanding your question?

Yes, I understand that this is a template of what user will get populated to the diagram once user releases button.
Here on my example, user takes a node from palette and drags it.
I want to know what actually happens when user does it, what makes this “placeholder” appear. I need to reproduce this programmatically.
If I would rephrase myself, I would put it like this
How can I programmatically set “placeholder” of an element that is about to be added to the diagram?

What the user is dragging is actually a copy of the node template that the target diagram would use for that data object. Are you asking for it to use something else instead of what it would naturally use?

If so, I’m not sure that’s easy to do for an external drag. For an internal drag, there’s an example in the NonRealtimeDraggingTool: Non-Realtime Dragging
In that extension what is being dragged is just an image of the original nodes and links that would have been dragged.

I can look into this later when I have more time.

I dont think that NonRealtimeDraggingTool will work for me.
Basically what I’m trying to achieve is next:

  1. user activates a special state where he can add a node to a diagram, simply by choosing an item from the list (lets say we have a list of nodes with checkboxes for each node)
    this selection happens outside of diagram context.
  2. After user selects an item, he moves mouse onto the diagram, so this selected node appear as “placeholder” next to the cursor
  3. user does click on the diagram, on click this node added to the diagram according to the current mouse location.

For me there is no question how to add node to the diagram from code. The only question is how I can add this “placeholder” of a node that user selected, that will make user to understand whats going on.
Is there a way to set/control this placeholder from code?
Thanks
Vlad

It sounds like you want a customized ClickCreatingTool.

Except for the custom cursor, it’s easy to set up what you want.

First, set ClickCreatingTool.isDoubleClick to false in your main/target Diagram.

Second, when in that special mode, set ClickCreatingTool.archetypeNodeData to the JavaScript Object that you want to be copied as the node data object for a new node that that tool inserts when the user clicks in the background of the diagram.

And I think you want to set the Diagram.currentCursor and Diagram.defaultCursor to a cursor that shows an image of the node to be created.

Here’s a sample that uses a Palette instead of plain HTML. But I hope the ideas are clear:

<!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>
    <div id="myDiagramDiv" style="flex-grow: 1; height: 400px; border: solid 1px black"></div>
  </div>

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

// initialize main Diagram
const myDiagram =
  $(go.Diagram, "myDiagramDiv",
    {
      "undoManager.isEnabled": true,
      "clickCreatingTool.isDoubleClick": false,
      "ModelChanged": e => myPalette.clearSelection()  // disable click from inserting a new node
    });

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: "", cursor: "pointer", fromLinkable: true, toLinkable: true,
      },
      new go.Binding("stroke", "color")),
    $(go.TextBlock,
      {
        margin: new go.Margin(5, 5, 3, 5),
        minSize: new go.Size(16, 16), maxSize: new go.Size(120, NaN),
        editable: true
      },
      new go.Binding("text").makeTwoWay())
  );

// initialize Palette
const myPalette =
  $(go.Palette, "myPaletteDiv",
    {
      nodeTemplateMap: myDiagram.nodeTemplateMap,
      "ChangedSelection": e => {
        const node = e.diagram.selection.first();
        if (node) {
          const imgdata = e.diagram.makeImageData({ parts: new go.List().add(node) });
          myDiagram.currentCursor = myDiagram.defaultCursor = `url(${imgdata})`;
          myDiagram.toolManager.clickCreatingTool.archetypeNodeData = node.data;
        } else {
          myDiagram.currentCursor = myDiagram.defaultCursor = "";
          myDiagram.toolManager.clickCreatingTool.archetypeNodeData = null;
        }
      },
      model: new go.GraphLinksModel([
        { text: "red node", color: "red" },
        { text: "green node", color: "green" },
        { text: "blue node", color: "blue" },
        { text: "orange node", color: "orange" }
      ])
    });

myDiagram.model = new 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}
  ]
});
  </script>
</body>
</html>

Alas, the cursor code shown above doesn’t work, even though I know the data URL produced by Diagram.makeImageData is correct. But if instead of “url(…)” as the cursor I just use “crosshair” as the cursor, everything works as expected. I don’t have time right now to investigate this.

It doesn’t seems like this effect can be done with setting a cursor, at least in NonRealtimeDraggingTool its not using cursor. there is _imagePart variable which sets template for current dragged part. I need to implement this same effect separately. Can you please elaborate on that?

OK, try this instead:

<!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>
    <div id="myDiagramDiv" style="flex-grow: 1; height: 400px; border: solid 1px black"></div>
  </div>

  <script src="https://unpkg.com/gojs"></script>
  <script id="code">

class CustomDraggingTool extends go.DraggingTool {
  // override an undocumented method to customize the external drag-and-drop behavior
  doSimulatedDragOver() {
    if (!this.mayDragIn()) return;
    // create an image of the selected Parts of myPalette,
    // which may look quite different from how the same data would look in this.diagram
    if (this.copiedParts === null) {
      if (myPalette.selection.count === 0) return;
      const img = myPalette.makeImage({ parts: myPalette.selection });
      const part =
        new go.Part({ layerName: "Tool", locationSpot: go.Spot.Center, location: this.diagram.lastInput.documentPoint })
          .add(new go.Picture({ element: img }));
      this.diagram.add(part);
      const map = new go.Map();
      map.add(part, new go.DraggingInfo());
      this.copiedParts = map;
    }
    // assume there is only one Part in the copiedParts collection --
    // the one holding an image of all of the selected Parts of the Palette
    this.copiedParts.first().key.location = this.diagram.lastInput.documentPoint;
  }
}

const $ = go.GraphObject.make;

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

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: "", cursor: "pointer", fromLinkable: true, toLinkable: true,
      },
      new go.Binding("stroke", "color")),
    $(go.TextBlock,
      {
        margin: new go.Margin(5, 5, 3, 5),
        minSize: new go.Size(16, 16), maxSize: new go.Size(120, NaN),
        editable: true
      },
      new go.Binding("text").makeTwoWay())
  );

// initialize Palette
const myPalette =
  $(go.Palette, "myPaletteDiv",
    {
      model: new go.GraphLinksModel([
        { text: "red node", color: "red" },
        { text: "green node", color: "green" },
        { text: "blue node", color: "blue" },
        { text: "orange node", color: "orange" }
      ])
    });

myDiagram.model = new 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}
  ]
});
  </script>
</body>
</html>