create-gojs-kit
Version:
A CLI for downloading GoJS samples, extensions, and docs
477 lines (425 loc) • 21.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="Create new collapsible groups, ungroup existing groups, and use drag-and-drop to change grouping
memberships." />
<meta itemprop="description" content="Create new collapsible groups, ungroup existing groups, and use drag-and-drop to change grouping
memberships." />
<meta property="og:description" content="Create new collapsible groups, ungroup existing groups, and use drag-and-drop to change grouping
memberships." />
<meta name="twitter:description" content="Create new collapsible groups, ungroup existing groups, and use drag-and-drop to change grouping
memberships." />
<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="Regrouping Editor for Dragging Nodes into and out of Groups" />
<meta property="og:title" content="Regrouping Editor for Dragging Nodes into and out of Groups" />
<meta name="twitter:title" content="Regrouping Editor for Dragging Nodes into and out of Groups" />
<meta property="og:image" content="https://gojs.net/latest/assets/images/screenshots/regrouping.png" />
<meta itemprop="image" content="https://gojs.net/latest/assets/images/screenshots/regrouping.png" />
<meta name="twitter:image" content="https://gojs.net/latest/assets/images/screenshots/regrouping.png" />
<meta property="og:url" content="https://gojs.net/latest/samples/regrouping.html" />
<meta property="twitter:url" content="https://gojs.net/latest/samples/regrouping.html" />
<meta name="twitter:card" content="summary_large_image" />
<meta property="og:type" content="website" />
<meta property="twitter:domain" content="gojs.net" />
<title>
Regrouping Editor for Dragging Nodes into and out of Groups | 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">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Lora&display=swap" rel="stylesheet">
<script id="code">
function init() {
myDiagram = new go.Diagram('myDiagramDiv', {
'InitialLayoutCompleted': e => updateTotalGroupDepth(),
// when a drag-drop occurs in the Diagram's background, make it a top-level node
mouseDrop: e => finishDrop(e, null),
layout: new go.GridLayout({ // Diagram has simple horizontal layout
wrappingWidth: Infinity,
alignment: go.GridAlignment.Position,
cellSize: new go.Size(1, 1)
}),
'commandHandler.archetypeGroupData': { isGroup: true, text: 'Group', horiz: false },
'undoManager.isEnabled': true
});
const colors = {
white: '#fffcf6',
blue: '#dfebe5',
darkblue: '#cae0d8',
yellow: '#fcdba2',
gray: '#59524c',
black: '#000000'
}
// The one template for Groups can be configured to either layout out its members
// horizontally or vertically, each with a different default color.
function makeLayout(horiz) { // a Binding conversion function
return new go.GridLayout({
wrappingColumn: horiz ? Infinity : 1,
alignment: go.GridAlignment.Position,
cellSize: new go.Size(1, 1),
spacing: horiz ? new go.Size(8, 8) : new go.Size(5, 5)
});
}
function defaultColor(horiz) { // a Binding conversion function
return horiz ? colors.white : colors.darkblue;
}
// this function is used to highlight a Group that the selection may be dropped into
function highlightGroup(e, grp, show) {
if (!grp) return;
e.handled = true;
if (show) {
// cannot depend on the grp.diagram.selection in the case of external drag-and-drops;
// instead depend on the DraggingTool.draggedParts or .copiedParts
var tool = grp.diagram.toolManager.draggingTool;
var map = tool.draggedParts || tool.copiedParts; // this is a Map
// now we can check to see if the Group will accept membership of the dragged Parts
if (grp.canAddMembers(map.toKeySet())) {
grp.isHighlighted = true;
return;
}
}
grp.isHighlighted = false;
}
// Upon a drop onto a Group, we try to add the selection as members of the Group.
// Upon a drop onto the background, or onto a top-level Node, make selection top-level.
// If this is OK, we're done; otherwise we cancel the operation to rollback everything.
function finishDrop(e, grp) {
var ok = (grp !== null
? grp.addMembers(grp.diagram.selection, true)
: e.diagram.commandHandler.addTopLevelParts(e.diagram.selection, true));
if (!ok) e.diagram.currentTool.doCancel();
const slider = document.getElementById('levelSlider');
const oldMax = parseInt(slider.max);
updateTotalGroupDepth();
const newMax = parseInt(slider.max);
// keep the slider value accurate to the current depth
slider.value = parseInt(slider.value) + newMax - oldMax;
}
myDiagram.groupTemplate =
new go.Group('Auto', {
ungroupable: true, // allow the user to remove the Group while keeping its members
// when selected, move the whole Group into the "Foreground" Layer
selectionChanged: g => {
const newlay = g.isSelected ? "Foreground" : "";
g.layerName = newlay;
g.findSubGraphParts().each(m => m.layerName = newlay);
},
// highlight when dragging into the Group
mouseDragEnter: (e, grp, prev) => highlightGroup(e, grp, true),
mouseDragLeave: (e, grp, next) => highlightGroup(e, grp, false),
computesBoundsAfterDrag: true, // don't stretch the Placeholder while dragging
computesBoundsIncludingLocation: true,
// when the selection is dropped into a Group, add the selected Parts into that Group;
// if it fails, cancel the tool, rolling back any changes
mouseDrop: finishDrop,
handlesDragDropForMembers: true, // don't need to define handlers on member Nodes and Links
// Groups containing Groups lay out their members horizontally
layout: makeLayout(false),
background: defaultColor(false) // default value if not specified in data
})
.bind('background', 'horiz', defaultColor)
.bind('layout', 'horiz', makeLayout)
.add(
new go.Shape('Rectangle', { stroke: colors.gray, strokeWidth: 1 })
.bindObject('fill', 'isHighlighted', h => h ? 'rgba(0,255,0,.3)' : 'transparent'),
new go.Panel('Vertical') // title above Placeholder
.add(
new go.Panel('Horizontal', { stretch: go.Stretch.Horizontal }) // button next to TextBlock
.add(
go.GraphObject.build('SubGraphExpanderButton', { alignment: go.Spot.Right, margin: 5 }),
new go.TextBlock({
alignment: go.Spot.Left,
editable: true,
margin: new go.Margin(6, 10, 6, 1),
font: 'bold 16px Lora, serif',
opacity: 0.95, // allow some color to show through
stroke: colors.black
})
.bind('font', 'horiz', horiz => horiz ? 'bold 20px Lora, serif' : 'bold 16px Lora, serif')
.bindTwoWay('text')
), // end Horizontal Panel
new go.Placeholder({ padding: 8, margin: 4, alignment: go.Spot.TopLeft })
) // end Vertical Panel
) // end Auto Panel
myDiagram.nodeTemplate =
new go.Node('Auto', {
// dropping on a Node is the same as dropping on its containing Group, even if it's top-level
mouseDrop: (e, node) => finishDrop(e, node.containingGroup),
isShadowed: true,
shadowBlur: 0,
shadowColor: colors.gray,
shadowOffset: new go.Point(4.5, 3.5)
})
.add(
new go.Shape('RoundedRectangle', { fill: colors.yellow, stroke: null, strokeWidth: 0 }),
new go.TextBlock({
margin: 8,
editable: true,
font: '13px Lora, serif'
})
.bindTwoWay('text')
);
// initialize the Palette and its contents
myPalette = new go.Palette('myPaletteDiv', {
nodeTemplateMap: myDiagram.nodeTemplateMap,
groupTemplateMap: myDiagram.groupTemplateMap
});
myPalette.model = new go.GraphLinksModel([
{ text: 'New Node', color: '#ACE600' },
{ isGroup: true, text: 'H Group', horiz: true },
{ isGroup: true, text: 'V Group', horiz: false }
]);
var slider = document.getElementById('levelSlider');
slider.addEventListener('change', reexpand);
slider.addEventListener('input', reexpand);
load();
}
function reexpand(e) {
myDiagram.commit(diag => {
var level = parseInt(document.getElementById('levelSlider').value);
diag.findTopLevelGroups().each(g => expandGroups(g, 0, level))
}, 'reexpand');
}
function expandGroups(g, i, level) {
if (!(g instanceof go.Group)) return;
g.isSubGraphExpanded = i < level;
g.memberParts.each(m => expandGroups(m, i + 1, level))
}
function updateTotalGroupDepth() {
let d = 0;
myDiagram.findTopLevelGroups().each(g => d = Math.max(d, groupDepth(g)));
document.getElementById('levelSlider').max = Math.max(0, d);
}
function groupDepth(g) {
if (!(g instanceof go.Group)) return 0;
let d = 1;
g.memberParts.each(m => d = Math.max(d, groupDepth(m)+1));
return d;
}
// save a model to and load a model from JSON text, displayed below the Diagram
function save() {
document.getElementById('mySavedModel').value = myDiagram.model.toJson();
myDiagram.isModified = false;
}
function load() {
myDiagram.model = go.Model.fromJson(document.getElementById('mySavedModel').value);
}
window.addEventListener('DOMContentLoaded', init);
</script>
<div id="sample">
<div style="width: 100%; display: flex; justify-content: space-between">
<div id="myPaletteDiv" style="width: 130px; margin-right: 2px; background-color: #dfebe5; border: solid 1px black">
</div>
<div id="myDiagramDiv" style="flex-grow: 1; height: 500px; background-color: #dfebe5; border: solid 1px black">
</div>
</div>
<p>
This sample allows the user to drag nodes, including groups, into and out of groups,
both from the Palette as well as from within the Diagram.
See the <a href="../intro/groups.html">Groups Intro page</a> for an explanation of GoJS Groups.
</p>
<p>
Highlighting to show feedback about potential addition to a group during a drag is implemented
using <a>GraphObject.mouseDragEnter</a> and <a>GraphObject.mouseDragLeave</a> event handlers.
Because <a>Group.computesBoundsAfterDrag</a> is true, the Group's <a>Placeholder</a>'s bounds are
not computed until the drop happens, so the group does not continuously expand as the user drags
a member of a group.
</p>
<p>
When a drop occurs on a Group or a regular Node, the <a>GraphObject.mouseDrop</a> event handler
adds the selection (the dragged Nodes) to the Group as new members.
The <a>Diagram.mouseDrop</a> event handler changes the dragged selected Nodes to be top-level,
rather than members of whatever Groups they had been in.
</p>
<p>
The slider controls how many nested levels of Groups are expanded. </br>
Semantic zoom level: <input id="levelSlider" type="range" min="0" max="5" />
</p>
<div id="buttons">
<button id="saveModel" onclick="save()">Save</button>
<button id="loadModel" onclick="load()">Load</button>
Diagram Model saved in JSON format:
</div>
<textarea id="mySavedModel" style="width:100%;height:300px">
{ "class": "go.GraphLinksModel",
"nodeDataArray": [
{"key":1, "isGroup":true, "text":"Main 1", "horiz":true},
{"key":2, "isGroup":true, "text":"Main 2", "horiz":true},
{"key":3, "isGroup":true, "text":"Group A", "group":1},
{"key":4, "isGroup":true, "text":"Group B", "group":1},
{"key":5, "isGroup":true, "text":"Group C", "group":2},
{"key":6, "isGroup":true, "text":"Group D", "group":2},
{"key":7, "isGroup":true, "text":"Group E", "group":6},
{"text":"First A", "group":3, "key":-7},
{"text":"Second A", "group":3, "key":-8},
{"text":"First B", "group":4, "key":-9},
{"text":"Second B", "group":4, "key":-10},
{"text":"Third B", "group":4, "key":-11},
{"text":"First C", "group":5, "key":-12},
{"text":"Second C", "group":5, "key":-13},
{"text":"First D", "group":6, "key":-14},
{"text":"First E", "group":7, "key":-15}
],
"linkDataArray": [ ]}
</textarea>
</div>
</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>Grid Layouts</h4>
<p>
This predefined layout is used for placing Nodes in a grid-like arrangement.
Nodes can be ordered, spaced apart, and wrapped as needed. This Layout ignores any Links connecting the nodes being laid out.
More information can be found in the <a href="../intro/layouts.html#GridLayout">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#gridlayout">Related samples</a>
</p>
<hr>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>Groups</h4>
<p>
The <a href="../api/symbols/Group.html" target="api">Group</a> class is used to treat a collection of <a href="../api/symbols/Node.html" target="api">Node</a>s and <a href="../api/symbols/Link.html" target="api">Link</a>s as if they were a single <a href="../api/symbols/Node.html" target="api">Node</a>.
Those nodes and links are members of the group; together they constitute a subgraph.
</p>
<p>
A subgraph is <em>not</em> another <a href="../api/symbols/Diagram.html" target="api">Diagram</a>, so there is no separate HTML Div element for the subgraph of a group.
All of the <a href="../api/symbols/Part.html" target="api">Part</a>s that are members of a <a href="../api/symbols/Group.html" target="api">Group</a> belong to the same Diagram as the Group.
There can be links between member nodes and nodes outside of the group as well as links between the group itself and other nodes.
There can even be links between member nodes and the containing group itself.
</p>
<p>
More information can be found in the <a href="../intro/groups.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#groups">Related samples</a>
</p>
<hr>
<!-- 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>Buttons</h4>
<p>
GoJS defines several <a href="../api/symbols/Panel.html" target="api">Panel</a>s for common uses.
These include "Button", "TreeExpanderButton", "SubGraphExpanderButton", "PanelExpanderButton", "ContextMenuButton", and "CheckBoxButton".
"ContextMenuButton"s are typically used inside of "ContextMenu" Panels;
"CheckBoxButton"s are used in the implementation of "CheckBox" Panels.
</p>
<p>
These predefined panels can be used as if they were <a href="../api/symbols/Panel.html" target="api">Panel</a>-derived classes in calls to <a href="../api/symbols/GraphObject.html#build" target="api">GraphObject.build</a>.
They are implemented as simple visual trees of <a href="../api/symbols/GraphObject.html" target="api">GraphObject</a>s in <a href="../api/symbols/Panel.html" target="api">Panel</a>s,
with pre-set properties and event handlers.
</p>
<p>
More information can be found in the <a href="../intro/buttons.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#buttons">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>