Need recommendations for drag and drop element on GoJS diagram and edges

Hi team,

I want to implement a feature to drag and drop HTML component on GoJS diagram.

Also, an additional feature that we want is to drag and drop elements on links so that the dragged elements gets appended between the nodes connecting the edges.

Can someone please help with the GoJS recommendations for above two features.

First, using HTML drag-and-drop:
HTML Drag and Drop, and external Clipboard pasting

Second, splicing nodes into links:
Flowgrammer
Splice Node Into Link
Also: Simple GoJS Sample
Also: Reordering Nodes

Thanks @walter

I checked the above links as recommended.

The examples above are using Palette to drag objects onto Diagram.

Can I also drag and drop between links and onto nodes without using Palette?

I want to use simple HTML container (containing HTML elements) and drag and drop it between links and onto nodes. Once dropped our code can handle element conversion to node shape template before adding to diagram.

Also, with the above requirement can I leverage mouseDragEnter, mouseDragLeave and mouseDrop events on Node and Link?

You are correct that only dragging from a Diagram to a Diagram has built-in support. The last sample I listed uses HTML drag-and-drop, but it does not implement splicing a node into a link.

When I get some free time I can try to create a sample for you.

@walter, is it possible to splice node into a link using HTML drag-and-drop API as well?

Can we get the target link or node where we want to drop element using HTML drag-and-drop API?

Try this, adapted from the HTML drag-and-drop sample, HTML Drag and Drop, and external Clipboard pasting

<!DOCTYPE html>
<html><body>
  <script src="https://unpkg.com/gojs"></script>
  <style>
    .draggable {
      font: bold 16px sans-serif;
      width: 140px;
      height: 20px;
      text-align: center;
      background: white;
      cursor: move;
      margin-top: 20px;
    }

    .palettezone {
      width: 160px;
      height: 400px;
      background: lightblue;
      padding: 10px;
      padding-top: 1px;
      float: left;
    }
  </style>
  <div id="sample">
    <div style="width: 100%; display: flex; justify-content: space-between">
      <div id="paletteZone" style="width: 160px; height: 400px; margin-right: 2px; background-color: lightblue; padding: 10px;">
        <div class="draggable" draggable="true">Water</div>
        <div class="draggable" draggable="true">Coffee</div>
        <div class="draggable" draggable="true">Tea</div>
      </div>
      <div id="myDiagramDiv" style="flex-grow: 1; height: 400px; border: solid 1px black"></div>
    </div>
    <input id="remove" type="checkbox" /><label for="remove">Remove HTML item after drag</label>
  </div>

  <script id="code">
// *********************************************************
// First, set up the infrastructure to do HTML drag-and-drop
// *********************************************************

var dragged = null; // A reference to the element currently being dragged

// This event should only fire on the drag targets.
// Instead of finding every drag target,
// we can add the event to the document and disregard
// all elements that are not of class "draggable"
document.addEventListener("dragstart", event => {
  if (event.target.className !== "draggable") return;
  // Some data must be set to allow drag
  event.dataTransfer.setData("text", event.target.textContent);

  // store a reference to the dragged element and the offset of the mouse from the center of the element
  dragged = event.target;
  dragged.offsetX = event.offsetX - dragged.clientWidth / 2;
  dragged.offsetY = event.offsetY - dragged.clientHeight / 2;
  // Objects during drag will have a red border
  event.target.style.border = "2px solid red";
}, false);

// This event resets styles after a drag has completed (successfully or not)
document.addEventListener("dragend", event => {
  // reset the border of the dragged element
  dragged.style.border = "";
  highlight(null);
}, false);

// Next, events intended for the drop target - the Diagram div

var div = document.getElementById("myDiagramDiv");
div.addEventListener("dragenter", event => {
  // Here you could also set effects on the Diagram,
  // such as changing the background color to indicate an acceptable drop zone

  // Requirement in some browsers, such as Internet Explorer
  event.preventDefault();
}, false);

div.addEventListener("dragover", event => {
  // We call preventDefault to allow a drop
  // But on divs that already contain an element,
  // we want to disallow dropping

  if (div === myDiagram.div) {
    var can = event.target;
    var pixelratio = myDiagram.computePixelRatio();

    // if the target is not the canvas, we may have trouble, so just quit:
    if (!(can instanceof HTMLCanvasElement)) return;

    var bbox = can.getBoundingClientRect();
    var bbw = bbox.width;
    if (bbw === 0) bbw = 0.001;
    var bbh = bbox.height;
    if (bbh === 0) bbh = 0.001;
    var mx = event.clientX - bbox.left * ((can.width / pixelratio) / bbw);
    var my = event.clientY - bbox.top * ((can.height / pixelratio) / bbh);
    var point = myDiagram.transformViewToDoc(new go.Point(mx, my));
    var part = myDiagram.findPartAt(point, true);
    highlight(part);
  }

  if (event.target.className === "dropzone") {
    // Disallow a drop by returning before a call to preventDefault:
    return;
  }

  // Allow a drop on everything else
  event.preventDefault();
}, false);

div.addEventListener("dragleave", event => {
  // reset background of potential drop target
  if (event.target.className == "dropzone") {
    event.target.style.background = "";
  }
  highlight(null);
}, false);

// handle the user option for removing dragged items from the Palette
var remove = document.getElementById('remove');

div.addEventListener("drop", event => {
  // prevent default action
  // (open as link for some elements in some browsers)
  event.preventDefault();

  // Dragging onto a Diagram
  if (div === myDiagram.div) {
    var can = event.target;
    var pixelratio = myDiagram.computePixelRatio();

    // if the target is not the canvas, we may have trouble, so just quit:
    if (!(can instanceof HTMLCanvasElement)) return;

    var bbox = can.getBoundingClientRect();
    var bbw = bbox.width;
    if (bbw === 0) bbw = 0.001;
    var bbh = bbox.height;
    if (bbh === 0) bbh = 0.001;
    var mx = event.clientX - bbox.left * ((can.width / pixelratio) / bbw);
    var my = event.clientY - bbox.top * ((can.height / pixelratio) / bbh);
    var point = myDiagram.transformViewToDoc(new go.Point(mx, my));
    myDiagram.startTransaction('new node');
    var newdata = {
      location: myDiagram.transformViewToDoc(new go.Point(mx - dragged.offsetX, my - dragged.offsetY)),
      text: event.dataTransfer.getData('text'),
      color: "lightyellow"
    };
    myDiagram.model.addNodeData(newdata);
    var newnode = myDiagram.findNodeForData(newdata);
    if (newnode) {
      myDiagram.select(newnode);
      dropped(newnode, point);
    }
    myDiagram.commitTransaction('new node');

    // remove dragged element from its old location
    if (remove.checked) dragged.parentNode.removeChild(dragged);
  }

  // If we were using drag data, we could get it here, ie:
  // var data = event.dataTransfer.getData('text');
}, false);


// highlight stationary nodes during an external drag-and-drop into a Diagram
function highlight(node) {  // may be null
  var oldskips = myDiagram.skipsUndoManager;
  myDiagram.skipsUndoManager = true;
  myDiagram.startTransaction("highlight");
  if (node !== null) {
    myDiagram.highlight(node);
  } else {
    myDiagram.clearHighlighteds();
  }
  myDiagram.commitTransaction("highlight");
  myDiagram.skipsUndoManager = oldskips;
}

// this is called upon an external drop in this diagram
function dropped(newNode, point) {
  const it = myDiagram.findPartsAt(point).iterator;
  while (it.next()) {
    const part = it.value;
    if (part === newNode) continue;
    if (part && part.mouseDrop) {
      const e = new go.InputEvent();
      e.diagram = myDiagram;
      e.documentPoint = point;
      e.viewPoint = myDiagram.transformDocToView(point);
      e.up = true;
      myDiagram.lastInput = e;
      // should be running in a transaction already
      part.mouseDrop(e, part);
      break;
    }
  }
}


// *********************************************************
// Second, set up a GoJS Diagram
// *********************************************************

// Since 2.2 you can also author concise templates with method chaining instead of GraphObject.make
// For details, see https://gojs.net/latest/intro/buildingObjects.html
const $ = go.GraphObject.make;  // for conciseness in defining templates

myDiagram = $(go.Diagram, "myDiagramDiv",  // create a Diagram for the DIV HTML element
  {
    layout: $(go.TreeLayout),
    "undoManager.isEnabled": true
  });

// define a simple Node template
myDiagram.nodeTemplate =
  $(go.Node, "Auto",
    { locationSpot: go.Spot.Center },
    new go.Binding('location'),
    $(go.Shape, "Rectangle",
      { fill: 'white' },
      // Shape.fill is bound to Node.data.color
      new go.Binding("fill", "color"),
      // this binding changes the Shape.fill when Node.isHighlighted changes value
      new go.Binding("fill", "isHighlighted", (h, shape) => {
        if (h) return "red";
        var c = shape.part.data.color;
        return c ? c : "white";
      }).ofObject()),  // binding source is Node.isHighlighted
    $(go.TextBlock,
      { margin: 3, font: "bold 16px sans-serif", width: 140, textAlign: 'center' },
      // TextBlock.text is bound to Node.data.key
      new go.Binding("text")),
    {
      mouseDragEnter: (e, node) => node.isHighlighted = true,
      mouseDragLeave: (e, node) => node.isHighlighted = false,
      mouseDrop: (e, node) => {
        const newnode = e.diagram.selection.first();
        const incoming = newnode.findLinksInto().first();
        if (incoming) e.diagram.remove(incoming);
        e.diagram.model.addLinkData( { from: node.key, to: newnode.key });
      }
    }
  );

myDiagram.linkTemplate =
  $(go.Link,
    $(go.Shape, { isPanelMain: true, strokeWidth: 6, stroke: "transparent" },
      new go.Binding("stroke", "isHighlighted", h => h ? "red" : "transparent").ofObject()),
    $(go.Shape, { isPanelMain: true, strokeWidth: 1.5 }),
    $(go.Shape, { toArrow: "Standard" }),
    {
      mouseDragEnter: (e, link) => link.isHighlighted = true,
      mouseDragLeave: (e, link) => link.isHighlighted = false,
      mouseDrop: (e, link) => {
        const oldto = link.toNode;
        const newnode = e.diagram.selection.first();
        if (oldto === newnode || link.fromNode === newnode) return;
        link.toNode = newnode;
        e.diagram.model.addLinkData({ from: newnode.key, to: oldto.key });
      }
    }
  );

// Modify the CommandHandler's doKeyDown to do nothing except bubble the event
// on a potential Paste command:
myDiagram.commandHandler.doKeyDown = function() {
  const diagram = this.diagram;
  const e = diagram.lastInput;
  // The meta (Command) key substitutes for "control" for Mac commands
  const control = e.meta || e.control;
  const shift = e.shift;
  const key = e.key;
  if ((control && key === 'V') || (shift && key === 'Insert')) {
    // Instead of the usual behavior:
    // if (this.canPasteSelection()) this.pasteSelection();
    // let the event bubble up the DOM:
    e.bubbles = true;
  } else {
    go.CommandHandler.prototype.doKeyDown.call(this, e);
  }
};

// handle inserting a new node using text that is in the system clipboard
document.addEventListener('paste', function (e) {
  const paste = e.clipboardData.getData("text");
  // Decide if the clipboard should be pasted, or if we should let GoJS paste
  // This sample pastes from the clipboard if it contains any text at all,
  // Otherwise, it pastes from GoJS
  if (paste.length > 0) {
    // Create a new node out of the text and paste it at the mouse location
    const loc = myDiagram.lastInput.documentPoint;
    myDiagram.model.addNodeData({ text: paste, location: loc });
  } else {
    // If the clipbooard does not contain anything, paste from GoJS instead
    const commandHandler = myDiagram.commandHandler;
    if (commandHandler.canPasteSelection()) commandHandler.pasteSelection();
  }
});

myDiagram.model = new go.GraphLinksModel(
  [
    { key: 1, text: "Alpha", color: "lightblue" },
    { key: 2, text: "Beta", color: "orange" },
    { key: 3, text: "Gamma", color: "lightgreen" },
    { key: 4, text: "Delta", color: "pink" }
  ],
  [
    { from: 1, to: 2 },
    { from: 1, to: 3 }
  ]);
  </script>
</body></html>

This code will form the basis of an improved HTML Drag Drop sample in the next release.