create-gojs-kit
Version:
A CLI for downloading GoJS samples, extensions, and docs
589 lines (528 loc) • 24.5 kB
HTML
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"/>
<meta name="description" content="Use HTML drag-and-drop to implement dragging HTML elements onto a GoJS Diagram to create new nodes. Also demonstrates pasting from the external clipboard." />
<meta itemprop="description" content="Use HTML drag-and-drop to implement dragging HTML elements onto a GoJS Diagram to create new nodes. Also demonstrates pasting from the external clipboard." />
<meta property="og:description" content="Use HTML drag-and-drop to implement dragging HTML elements onto a GoJS Diagram to create new nodes. Also demonstrates pasting from the external clipboard." />
<meta name="twitter:description" content="Use HTML drag-and-drop to implement dragging HTML elements onto a GoJS Diagram to create new nodes. Also demonstrates pasting from the external clipboard." />
<link rel="preconnect" href="https://rsms.me/">
<link rel="stylesheet" href="../assets/css/style.css">
<!-- Copyright 1998-2025 by Northwoods Software Corporation. -->
<meta itemprop="name" content="Drag and Drop From HTML Element to Diagram" />
<meta property="og:title" content="Drag and Drop From HTML Element to Diagram" />
<meta name="twitter:title" content="Drag and Drop From HTML Element to Diagram" />
<meta property="og:image" content="https://gojs.net/latest/assets/images/screenshots/htmldragdrop.png" />
<meta itemprop="image" content="https://gojs.net/latest/assets/images/screenshots/htmldragdrop.png" />
<meta name="twitter:image" content="https://gojs.net/latest/assets/images/screenshots/htmldragdrop.png" />
<meta property="og:url" content="https://gojs.net/latest/samples/htmlDragDrop.html" />
<meta property="twitter:url" content="https://gojs.net/latest/samples/htmlDragDrop.html" />
<meta name="twitter:card" content="summary_large_image" />
<meta property="og:type" content="website" />
<meta property="twitter:domain" content="gojs.net" />
<title>
Drag and Drop From HTML Element to Diagram | GoJS Diagramming Library
</title>
</head>
<body>
<!-- This top nav is not part of the sample code -->
<nav id="navTop" class=" w-full h-[var(--topnav-h)] z-30 bg-white border-b border-b-gray-200">
<div class="max-w-screen-xl mx-auto flex flex-wrap items-start justify-between px-4">
<a class="text-white bg-nwoods-primary font-bold !leading-[calc(var(--topnav-h)_-_1px)] my-0 px-2 text-4xl lg:text-5xl logo"
href="../">
GoJS
</a>
<div class="relative">
<button id="topnavButton" class="h-[calc(var(--topnav-h)_-_1px)] px-2 m-0 text-gray-900 bg-inherit shadow-none md:hidden hover:!bg-inherit hover:!text-nwoods-accent hover:!shadow-none" aria-label="Navigation">
<svg class="h-7 w-7 block" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div id="topnavList" class="hidden md:block">
<div class="absolute right-0 z-30 flex flex-col items-end rounded border border-gray-200 p-4 pl-12 shadow bg-white text-gray-900 font-semibold
md:flex-row md:space-x-4 md:items-start md:border-0 md:p-0 md:shadow-none md:bg-inherit">
<a href="../learn/">Learn</a>
<a href="../samples/">Samples</a>
<a href="../intro/">Intro</a>
<a href="../api/">API</a>
<a href="../download.html">Download</a>
<a href="https://forum.nwoods.com/c/gojs/11" target="_blank" rel="noopener">Forum</a>
<a id="tc" href="https://nwoods.com/contact.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/contact.html', 'contact');">Contact</a>
<a id="tb" href="https://nwoods.com/sales/index.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/sales/index.html', 'buy');">Buy</a>
</div>
</div>
</div>
</div>
</nav>
<script>
window.addEventListener("DOMContentLoaded", function () {
// topnav
var topButton = document.getElementById("topnavButton");
var topnavList = document.getElementById("topnavList");
if (topButton && topnavList) {
topButton.addEventListener("click", function (e) {
topnavList
.classList
.toggle("hidden");
e.stopPropagation();
});
document.addEventListener("click", function (e) {
// if the clicked element isn't the list, close the list
if (!topnavList.classList.contains("hidden") && !e.target.closest("#topnavList")) {
topButton.click();
}
});
// set active <a> element
var url = window
.location
.href
.toLowerCase();
var aTags = topnavList.getElementsByTagName('a');
for (var i = 0; i < aTags.length; i++) {
var lowerhref = aTags[i]
.href
.toLowerCase();
if (lowerhref.endsWith('.html'))
lowerhref = lowerhref.slice(0, -5);
if (url.startsWith(lowerhref)) {
aTags[i]
.classList
.add('active');
break;
}
}
}
});
</script>
<div class="flex flex-col prose">
<div class="w-full max-w-screen-xl mx-auto">
<!-- * * * * * * * * * * * * * -->
<!-- Start of GoJS sample code -->
<script src="https://cdn.jsdelivr.net/npm/gojs@3.1.0"></script>
<div id="allSampleContent" class="p-4 w-full">
<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="removeCheckBox" type="checkbox" /><label for="removeCheckBox"
>Remove HTML item after drop</label
>
<p>
The "Palette" in this sample is not a <a>Palette</a> (or GoJS component) at all. It is a
collection of HTML elements with draggable attributes using the
<a href="https://developer.mozilla.org/en-US/docs/DragDrop/Drag_and_Drop"
>HTML Drag and Drop API</a
>.
</p>
<p>
This sample lets you drag these HTML elements onto the Diagram to create GoJS nodes. As the
mouse passes over stationary nodes or links in the Diagram, they are highlighted.
</p>
<p>
If a drop happens (based on the mouse point) on an existing node, a new link is also drawn from
that existing node to the newly dropped node. If a drop happens on an existing link, the link is
reconnected to go to the newly dropped node, and a new link is added to go from that newly
dropped node to whatever node the link had been connected to before.
</p>
<p>
This sample also demonstrates allowing external clipboard pasting, by modifying
<code>myDiagram.commandHandler.doKeyDown</code> to do nothing but allow the event to bubble, and
then defining a <code>"paste"</code> event on the <code>document</code>. So the user can select
some text, either on the page or in some other app, and then a paste in the diagram will create
a new node using that text.
</p>
</div>
<script id="code">
// *********************************************************
// First, set up the infrastructure to do HTML drag-and-drop
// *********************************************************
function init() {
let 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 = '';
onHighlight(null);
},
false
);
// Next, events intended for the drop target - the Diagram div
const 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) {
const can = event.target;
const pixelratio = myDiagram.computePixelRatio();
// if the target is not the canvas, we may have trouble, so just quit:
if (!(can instanceof HTMLCanvasElement)) return;
const bbox = can.getBoundingClientRect();
let bbw = bbox.width;
if (bbw === 0) bbw = 0.001;
let bbh = bbox.height;
if (bbh === 0) bbh = 0.001;
const mx = event.clientX - bbox.left * (can.width / pixelratio / bbw);
const my = event.clientY - bbox.top * (can.height / pixelratio / bbh);
const point = myDiagram.transformViewToDoc(new go.Point(mx, my));
const part = myDiagram.findPartAt(point, true);
onHighlight(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 = '';
}
onHighlight(null);
},
false
);
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) {
const can = event.target;
const pixelratio = myDiagram.computePixelRatio();
// if the target is not the canvas, we may have trouble, so just quit:
if (!(can instanceof HTMLCanvasElement)) return;
const bbox = can.getBoundingClientRect();
let bbw = bbox.width;
if (bbw === 0) bbw = 0.001;
let bbh = bbox.height;
if (bbh === 0) bbh = 0.001;
const mx = event.clientX - bbox.left * (can.width / pixelratio / bbw);
const my = event.clientY - bbox.top * (can.height / pixelratio / bbh);
const point = myDiagram.transformViewToDoc(new go.Point(mx, my));
// // if there's nothing at that point
// if (myDiagram.findPartAt(point) === null) {
// // a return here doesn't allow the drop to happen
// return;
// }
// otherwise create a new node at the drop point
myDiagram.startTransaction('new node');
const newdata = {
// assuming the locationSpot is Spot.Center:
location: myDiagram.transformViewToDoc(
new go.Point(mx - dragged.offsetX, my - dragged.offsetY)
),
text: event.dataTransfer.getData('text'),
color: 'lightyellow'
};
myDiagram.model.addNodeData(newdata);
const newnode = myDiagram.findNodeForData(newdata);
if (newnode) {
newnode.ensureBounds();
myDiagram.select(newnode);
onDrop(newnode, point);
}
myDiagram.commitTransaction('new node');
// remove dragged element from its old location, if checkbox is checked
if (document.getElementById('removeCheckBox').checked) {
dragged.parentNode.removeChild(dragged);
}
}
// If we were using drag data, we could get it here, ie:
// const data = event.dataTransfer.getData('text');
},
false
);
// this is called on a stationary node or link during an external drag-and-drop into a Diagram
function onHighlight(part) {
// may be null
const oldskips = myDiagram.skipsUndoManager;
myDiagram.skipsUndoManager = true;
myDiagram.startTransaction('highlight');
if (part !== null) {
myDiagram.highlight(part);
} else {
myDiagram.clearHighlighteds();
}
myDiagram.commitTransaction('highlight');
myDiagram.skipsUndoManager = oldskips;
}
// this is called upon an external drop in this diagram,
// after a new node has been created and selected
function onDrop(newNode, point) {
// look for a drop directly onto a Node or Link
const it = myDiagram.findPartsAt(point).iterator;
while (it.next()) {
const part = it.value;
if (part === newNode) continue;
// the drop happened on some Part -- call its mouseDrop handler
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);
return;
}
}
// didn't find anything to drop onto
}
// *****************************
// Second, set up a GoJS Diagram
// *****************************
const myDiagram = new go.Diagram('myDiagramDiv', {
layout: new go.TreeLayout(),
'undoManager.isEnabled': true
});
// define a Node template
myDiagram.nodeTemplate =
new go.Node('Auto', {
locationSpot: go.Spot.Center,
// on mouse-over, highlight the node
mouseDragEnter: (e, node) => (node.isHighlighted = true),
mouseDragLeave: (e, node) => (node.isHighlighted = false),
// on a mouse-drop add a link from the dropped-upon node to the new node
mouseDrop: (e, node) => {
const newnode = e.diagram.selection.first();
if (!mayConnect(node, newnode)) return;
const incoming = newnode.findLinksInto().first();
if (incoming) e.diagram.remove(incoming);
e.diagram.model.addLinkData({ from: node.key, to: newnode.key });
}
})
.bind('location')
.add(
new go.Shape('Rectangle', { fill: 'white' })
// Shape.fill is bound to Node.data.color
.bind('fill', 'color')
// this binding changes the Shape.fill when Node.isHighlighted changes value
.bindObject('fill', 'isHighlighted', (h, shape) => {
if (h) return 'red';
const c = shape.part.data.color;
return c ? c : 'white';
}),
new go.TextBlock({ margin: 3, font: 'bold 16px sans-serif', width: 140, textAlign: 'center' })
// TextBlock.text is bound to Node.data.text
.bind('text')
);
// define a Link template
myDiagram.linkTemplate =
new go.Link({
// on mouse-over, highlight the link
mouseDragEnter: (e, link) => (link.isHighlighted = true),
mouseDragLeave: (e, link) => (link.isHighlighted = false),
// on a mouse-drop splice the new node in between the dropped-upon link's fromNode and toNode
mouseDrop: (e, link) => {
const oldto = link.toNode;
const newnode = e.diagram.selection.first();
if (!mayConnect(newnode, oldto)) return;
if (!mayConnect(link.fromNode, newnode)) return;
link.toNode = newnode;
e.diagram.model.addLinkData({ from: newnode.key, to: oldto.key });
}
})
.add(
// two path Shapes: the transparent one becomes visible during mouse-over
new go.Shape({ isPanelMain: true, strokeWidth: 6, stroke: 'transparent' })
.bindObject('stroke', 'isHighlighted', h => h ? 'red' : 'transparent'),
new go.Shape({ isPanelMain: true, strokeWidth: 1 }),
new go.Shape({ toArrow: 'Standard' })
);
// Decide whether a link from node1 to node2 may be created by a drop operation
function mayConnect(node1, node2) {
return node1 !== node2;
}
// Modify the CommandHandler's doKeyDown to do nothing except bubble the event
// on a potential Paste command:
myDiagram.commandHandler.doKeyDown = function () {
// method override must be function, not =>
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 k = e.commandKey;
if (((control && k === 'v') || (shift && k === 'Insert')) &&
(!diagram.commandHandler.canPasteSelection() || diagram.selection.count === 0)) {
// 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);
}
};
// handle inserting a new node using text that is in the system clipboard
document.addEventListener('paste', 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;
const newdata = { text: paste, location: loc };
myDiagram.model.addNodeData(newdata);
const newnode = myDiagram.findNodeForData(newdata);
if (newnode) myDiagram.select(newnode);
// clear the GoJS clipboard
myDiagram.commandHandler.copyToClipboard(null);
} 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 }
]
);
}
init();
</script>
</div>
<!-- * * * * * * * * * * * * * -->
<!-- End of GoJS sample code -->
</div>
<div id="allTagDescriptions" class="p-4 w-full max-w-screen-xl mx-auto">
<hr/>
<h3 class="text-xl">GoJS Features in this sample</h3>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>Palette</h4>
<p>
A <a href="../api/symbols/Palette.html" target="api">Palette</a> is a subclass of <a href="../api/symbols/Diagram.html" target="api">Diagram</a> that is used to display a number of <a href="../api/symbols/Part.html" target="api">Part</a>s that
can be dragged into the diagram that is being modified by the user.
The initialization of a <a href="../api/symbols/Palette.html" target="api">Palette</a> is just like the initialization of any <a href="../api/symbols/Diagram.html" target="api">Diagram</a>.
Like Diagrams, you can have more than one Palette on the page at the same time.
</p>
<p>
More information can be found in the <a href="../intro/palette.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#palette">Related samples</a>
</p>
<hr>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>HTML Interaction</h4>
<p>
GoJS Diagrams can be used alongside other HTML elements in a webapp.
For custom Text Editors, Context Menus, and ToolTips, which are invoked and hidden via GoJS tool operations, it is best to use the <a href="../api/symbols/HTMLInfo.html" target="api">HTMLInfo</a> class.
</p>
<p>
More information can be found in the <a href="../intro/HTMLinteraction.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#html">Related samples</a>
</p>
<hr>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>Commands</h4>
<p>
A <a href="../api/symbols/CommandHandler.html" target="api">CommandHandler</a> handles all default keyboard input events in a Diagram.
There are many predefined methods on <a>CommandHandler</a> that implement common commands to operate on the Diagram or the current <a>Diagram.selection></a>.
</p>
<p>
You can override <a>CommandHandler.doKeyDown</a> to handle additional keyboard shortcuts or to change which commands are invoked via the keyboard.
<p>
Your code can invoke a command by calling the appropriate method on the <a>Diagram.commandHandler</a>.
Each command method has a corresponding <b>can...</b> predicate that your code can use to enable or disable any buttons that invoke the command.
Your code can customize the behavior of a command by overriding the method on <a>CommandHandler</a>,
or by setting properties on the <a>CommandHandler</a> or <a>Diagram</a> or <a>Part</a>s --
see <a href="../intro/permissions.html">GoJS Permissions</a>.
</p>
<p>
There are several CommandHandler extensions that provide additional functionality.
<p>
More information can be found in the <a href="../intro/commands.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#commands">Related samples</a>
</p>
<hr>
</div>
</div>
</body>
<!-- This script is part of the gojs.net website, and is not needed to run the sample -->
<script src="../assets/js/goSamples.js"></script>
</html>