create-gojs-kit
Version:
A CLI for downloading GoJS samples, extensions, and docs
635 lines (597 loc) • 26.6 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"/>
<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="Context Menus" />
<meta property="og:title" content="Context Menus" />
<meta name="twitter:title" content="Context Menus" />
<meta property="og:image" content="https://gojs.net/latest/assets/images/fp/defaultCard.png" />
<meta itemprop="image" content="https://gojs.net/latest/assets/images/fp/defaultCard.png" />
<meta name="twitter:image" content="https://gojs.net/latest/assets/images/fp/defaultCard.png" />
<meta property="og:url" content="https://gojs.net/latest/intro/contextMenus.html" />
<meta property="twitter:url" content="https://gojs.net/latest/intro/contextMenus.html" />
<meta name="twitter:card" content="summary_large_image" />
<meta property="og:type" content="website" />
<meta property="twitter:domain" content="gojs.net" />
<title>
Context Menus | GoJS
</title>
<link rel="stylesheet" href="../assets/css/prism.css"/>
</head>
<script>
window.diagrams = [];
window.goCode = function (pre, w, h, parentid, animation) {
window
.diagrams
.push([pre, w, h, parentid, animation]);
}
</script>
<body>
<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="sticky top-0 left-0 z-10 px-2 w-full bg-white border-b border-b-gray-200 md:hidden">
<button id="sidenavButton" class="flex p-2 text-gray-900 bg-inherit shadow-none items-center text-sm font-semibold hover:!bg-inherit hover:!text-nwoods-accent hover:!shadow-none" aria-label="Navigation">
<svg class="h-7 w-7 block mr-2" 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>
<span>Menu</span>
</button>
</div>
<script>
window.addEventListener("DOMContentLoaded", function () {
// sidenav
var sideButton = document.getElementById("sidenavButton");
var sidenav = document.getElementById("sidenav");
if (sideButton && sidenav) {
sideButton.addEventListener("click", function (e) {
sidenav
.classList
.toggle("hidden");
e.stopPropagation();
});
document.addEventListener("click", function (e) {
// if the clicked element isn't the list, close the list
if (!sidenav.classList.contains("hidden") && !e.target.closest("#sidenavList")) {
sideButton.click();
}
});
}
});
</script>
<div class="flex flex-row md:min-h-screen w-full max-w-screen-xl mx-auto">
<aside id="sidenav"
class="hidden fixed top-0 left-0 z-10 w-full bg-black/10 min-h-screen max-h-screen overflow-x-hidden overflow-y-auto shrink-0
md:block md:sticky md:w-52 md:min-h-0 md:bg-inherit md:border-r md:border-r-gray-200 md:overscroll-auto">
<nav id="sidenavList" class="flex flex-col bg-white w-52 min-h-screen pl-2 pt-4 pb-24 md:w-full md:min-h-0">
<a href="index.html">Basics</a>
<a href="buildingObjects.html">Building Parts</a>
<a href="usingModels.html">Using Models</a>
<a href="dataBinding.html">Data Binding</a>
<a href="react.html">GoJS with React</a>
<a href="svelte.html">GoJS with Svelte</a>
<a href="angular.html">GoJS with Angular</a>
<a href="textBlocks.html">TextBlocks</a>
<a href="shapes.html">Shapes</a>
<a href="pictures.html">Pictures</a>
<a href="panels.html">Panels</a>
<a href="tablePanels.html">Table Panels</a>
<a href="brush.html">Brushes</a>
<a href="sizing.html">Sizing Objects</a>
<a href="itemArrays.html">Item Arrays</a>
<a href="changedEvents.html">Changed Events</a>
<a href="transactions.html">Transactions</a>
<a href="viewport.html">Coordinates</a>
<a href="initialView.html">Initial View</a>
<a href="collections.html">Collections</a>
<a href="links.html">Links</a>
<a href="linkLabels.html">Link Labels</a>
<a href="connectionPoints.html">Link Points</a>
<a href="ports.html">Ports</a>
<a href="nodes.html">Nodes</a>
<a href="typings.html">Typings</a>
<a href="debugging.html">Debugging</a>
<a href="layouts.html">Layouts</a>
<a href="routers.html">Routers</a>
<a href="trees.html">Trees</a>
<a href="subtrees.html">SubTrees</a>
<a href="groups.html">Groups</a>
<a href="subgraphs.html">SubGraphs</a>
<a href="sizedGroups.html">Sized Groups</a>
<a href="selection.html">Selection</a>
<a href="highlighting.html">Highlighting</a>
<a href="theming.html">Theming</a>
<a href="tooltips.html">ToolTips</a>
<a href="contextMenus.html">Context Menus</a>
<a href="events.html">Diagram Events</a>
<a href="tools.html">Tools</a>
<a href="commands.html">Commands</a>
<a href="accessibility.html">Accessibility</a>
<a href="buttons.html">Buttons</a>
<a href="permissions.html">Permissions</a>
<a href="validation.html">Validation</a>
<a href="animation.html">Animation</a>
<a href="HTMLInteraction.html">HTML Interaction</a>
<a href="layers.html">Layers & Z-ordering</a>
<a href="palette.html">Palette</a>
<a href="overview.html">Overview</a>
<a href="replacingDeleting.html">Replacing and Deleting</a>
<a href="templateMaps.html">Template Maps</a>
<a href="legends.html">Legends and Titles</a>
<a href="extensions.html">Extensions</a>
<a href="geometry.html">Geometry Strings</a>
<a href="grids.html">Grid Patterns</a>
<a href="graduatedPanels.html">Graduated Panels</a>
<a href="SVGContext.html">Rendering to SVG</a>
<a href="makingSVG.html">Snapshot to SVG</a>
<a href="makingImages.html">Diagram Images</a>
<a href="printing.html">Printing</a>
<a href="serverSideImages.html">Server-side Images</a>
<a href="nodeScript.html">GoJS in Node.js</a>
<a href="testing.html">Testing</a>
<a href="performance.html">Performance</a>
<a href="platforms.html">Platforms</a>
<a href="deployment.html">Deployment</a>
</nav>
</aside>
<script>
var navList = document.getElementById('sidenavList');
if (navList !== null) {
var url = window.location.href;
var lindex = url.lastIndexOf('/');
url = url
.slice(lindex + 1)
.toLowerCase();
var aTags = navList.getElementsByTagName('a');
var currentindex = -1;
for (var i = 0; i < aTags.length; i++) {
var lowerhref = aTags[i]
.href
.toLowerCase();
if (lowerhref.indexOf('/' + url) !== -1) {
currentindex = i;
aTags[i]
.classList
.add('active');
break;
}
}
}
</script>
<div class="px-4 pb-16 w-full overflow-hidden prose">
<h1>Context Menus</h1>
<p><b>GoJS</b> provides a mechanism for you to define context menus for any object or for the diagram background.</p>
<p class="box" style="background-color: lightgoldenrodyellow">
Note: GoJS context menus cannot render outside of Diagrams,
because they are objects inside the Diagram and therefore drawn only on the Diagram.
If you need a context menu that may be drawn partially or fully outside the Diagram,
use an <a href="#HTMLContextMenus">HTML context menu</a>.
</p>
<p>
A GoJS context menu is an <a>Adornment</a> that is shown when the user context-clicks
(right mouse click or long touch hold) an object that has its <a>GraphObject.contextMenu</a> set.
The context menu is bound to the same data as the part itself.
</p>
<p>See samples that make use of context menus in the <a href="../samples/index.html#contextmenus">samples index</a>.</p>
<p>
It is typical to implement a context menu as a "ContextMenu" Panel containing "ContextMenuButton"s,
as you can see in the code below in the assignment of the Node's <a>GraphObject.contextMenu</a> and
<a>Diagram.contextMenu</a> properties.
Each "ContextMenu" is just a "Vertical" Panel <a>Adornment</a> that is shadowed.
Each "ContextMenuButton" is a Panel on which you can set the <a>GraphObject.click</a> event handler.
In the event handler <code>obj.part</code> will be the whole context menu Adornment.
<code>obj.part.adornedPart</code> will be the adorned Node or Link.
The bound data is <code>obj.part.data</code>, which will be the same as <code>obj.part.adornedPart.data</code>.
</p>
<p>
You can see how the "ContextMenu" and "ContextMenuButton" builders are defined at
<a href="../extensions/Buttons.js">Buttons.js</a>.
There are examples of customizing buttons at <a href="buttons.html">Introduction to Buttons</a>.
You may also wish to theme your own context menus,
described on the <a href="./theming.html#BuilderObjects">theming intro page</a>.
</p>
<p>
In this example each <a>Node</a> has its <a>GraphObject.contextMenu</a> property set to an Adornment
that shows a single button that when clicked changes the color property of the bound model data.
The diagram gets its own context menu by setting <a>Diagram.contextMenu</a>.
</p>
<pre class="lang-js" id="contextmenus"><code>
// This method is called as a context menu button's click handler.
// Rotate the selected node's color through a predefined sequence of colors.
function changeColor(e, obj) {
diagram.commit(d => {
// get the context menu that holds the button that was clicked
const contextmenu = obj.part;
// get the node data to which the Node is data bound
const nodedata = contextmenu.data;
// compute the next color for the node
let newcolor = "lightblue";
switch (nodedata.color) {
case "lightblue": newcolor = "lightgreen"; break;
case "lightgreen": newcolor = "lightyellow"; break;
case "lightyellow": newcolor = "orange"; break;
case "orange": newcolor = "lightblue"; break;
}
// modify the node data
// this evaluates data Bindings and records changes in the UndoManager
d.model.set(nodedata, "color", newcolor);
}, "changed color");
}
// this is a normal Node template that also has a contextMenu defined for it
diagram.nodeTemplate =
new go.Node("Auto", {
contextMenu: // define a context menu for each node
go.GraphObject.build("ContextMenu") // that has one button
.add(
go.GraphObject.build("ContextMenuButton", {
click: changeColor,
"ButtonBorder.fill": "white",
"_buttonFillOver": "skyblue",
})
.add(new go.TextBlock("Change Color"))
// more ContextMenuButtons would go here
)
// end Adornment
})
.add(
new go.Shape("RoundedRectangle", { fill: "white" })
.bind("fill", "color"),
new go.TextBlock({ margin: 5 })
.bind("text")
);
// also define a context menu for the diagram's background
diagram.contextMenu =
go.GraphObject.build("ContextMenu")
.add(
go.GraphObject.build("ContextMenuButton", {
click: (e, obj) => e.diagram.commandHandler.undo()
})
.add(new go.TextBlock("Undo"))
.bindObject("visible", "", o => o.diagram.commandHandler.canUndo()),
go.GraphObject.build("ContextMenuButton", {
click: (e, obj) => e.diagram.commandHandler.redo()
})
.add(new go.TextBlock("Redo"))
.bindObject("visible", "", o => o.diagram.commandHandler.canRedo()),
// no binding, always visible button:
go.GraphObject.build("ContextMenuButton", {
click: (e, obj) => {
e.diagram.commit(d => {
const data = {};
d.model.addNodeData(data);
part = d.findPartForData(data); // must be same data reference, not a new {}
// set location to saved mouseDownPoint in ContextMenuTool
part.location = d.toolManager.contextMenuTool.mouseDownPoint;
}, 'new node');
}
})
.add(new go.TextBlock("New Node"))
);
const nodeDataArray = [
{ key: 1, text: "Alpha", color: "lightyellow" },
{ key: 2, text: "Beta", color: "orange" }
];
const linkDataArray = [
{ from: 1, to: 2 }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
diagram.undoManager.isEnabled = true;
</code></pre>
<script>
goCode('contextmenus', 350, 200);
</script>
<p>
Try context clicking a node and invoking the "Change Color" command a few times.
With the diagram context menu you will be able to "Undo" and/or "Redo",
or you can use Control-Z and/or Control-Y.
</p>
<h2 id="Positioning">Positioning</h2>
<p>
There are two ways to customize the positioning of the context menu relative to the adorned GraphObject.
One way is to override <a>ContextMenuTool.positionContextMenu</a>.
Another way is to have the context menu <a>Adornment</a> include a <a>Placeholder</a>.
The Placeholder is positioned to have the same size and position as the adorned object.
The context menu will not to have a background, and thus will not display a shadow by
default when using a Placeholder.
</p>
<pre class="lang-js" id="contextmenusplaceholder"><code>
// this is a shared context menu button click event handler, just for demonstration
function cmCommand(e, obj) {
const node = obj.part.adornedPart; // the Node with the context menu
const buttontext = obj.elt(1); // the TextBlock
alert(buttontext.text + " command on " + node.data.text);
}
// this is a normal Node template that also has a contextMenu defined for it
diagram.nodeTemplate =
new go.Node("Auto", {
contextMenu: // define a context menu for each node
go.GraphObject.build("ContextMenu", // that has several buttons around
{ type: go.Panel.Spot })
.add( // a Placeholder object
new go.Placeholder({ padding: 5 }),
go.GraphObject.build("ContextMenuButton",
{ alignment: go.Spot.Top, alignmentFocus: go.Spot.Bottom, click: cmCommand })
.add(new go.TextBlock("Top")),
go.GraphObject.build("ContextMenuButton",
{ alignment: go.Spot.Right, alignmentFocus: go.Spot.Left, click: cmCommand })
.add(new go.TextBlock("Right")),
go.GraphObject.build("ContextMenuButton",
{ alignment: go.Spot.Bottom, alignmentFocus: go.Spot.Top, click: cmCommand })
.add(new go.TextBlock("Bottom")),
go.GraphObject.build("ContextMenuButton",
{ alignment: go.Spot.Left, alignmentFocus: go.Spot.Right, click: cmCommand })
.add(new go.TextBlock("Left"))
) // end Adornment
})
.add(
new go.Shape("RoundedRectangle", { fill: "white" })
.bind("fill", "color"),
new go.TextBlock({ margin: 5 })
.bind("text")
);
const nodeDataArray = [
{ key: 1, text: "Alpha", color: "lightyellow" },
{ key: 2, text: "Beta", color: "orange" }
];
const linkDataArray = [
{ from: 1, to: 2 }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</code></pre>
<script>
goCode('contextmenusplaceholder', 350, 200);
</script>
<h2 id="HTMLContextMenus">HTML Context Menus</h2>
<p>
It is possible to define custom context menus using HTML instead of Adornments
by using the <a>HTMLInfo</a> class.
The <a href="../samples/customContextMenu.html">Custom Context Menu sample</a> and
<a href="../samples/htmlLightBoxContextMenu.html">Lightbox Context Menu sample</a>
show two such custom context menus.
</p>
<p>
HTML context menus require more effort to implement than using the default <b>GoJS</b> "ContextMenu" and "ContextMenuButton".
However you would have the full power of HTML/CSS/JavaScript to show whatever you want.
This includes creating context menus that can exist or float outside of the Diagram.
</p>
<p>
There are two primary considerations when authoring HTML and CSS for context menus.
The context menu should usually be a sibling Element of the Diagram and should never be nested
inside a Diagram DIV:
</p>
<pre class="lang-js"><code>
<div style="position: relative;">
<div id="myDiagramDiv" style="border: solid 1px black; width:400px; height:400px;"></div>
<div id="contextMenu">
<!-- ... context menu HTML -->
</div>
</div>
</code></pre>
<p>And the ContextMenu may need a z-index set to ensure it is always on top.
GoJS Diagrams have z-index of 2, and some tools a z-index of 100.</p>
<pre class="lang-css"><code>#contextMenu {
z-index: 1000;
...
}
</code></pre>
<p>
See the <a href="../samples/customContextMenu.html">Custom Context Menu sample</a> and
<a href="../samples/htmlLightBoxContextMenu.html">Lightbox Context Menu sample</a> for HTML examples.
See the <a href="HTMLInteraction.html">HTMLInteraction</a> page for more discussion on HTML in GoJS.
</p>
<h2 id="DefaultContextMenuForTouchEnabledDevices">Default Context Menu for Touch-enabled devices</h2>
<p>
Touch devices are presumed to have no keyboard ability, which makes actions like copying and pasting more difficult.
Because of this, <b>GoJS</b> provides a built-in default context menu on touch devices, implemented in HTML.
The buttons on this menu are populated dynamically, depending on the target GraphObject (if any) and
Diagram and their properties.
</p>
<p>
The default context menu can be disabled by setting <a>ContextMenuTool.defaultTouchContextMenu</a> to null.
The <a href="../samples/htmlLightBoxContextMenu.html">Lightbox Context Menu sample</a> contains a
re-implementation of this menu if you wish to modify it.
</p>
<p>
If you define your own custom context menus,
they will prevent the default context menu from appearing on touch devices.
We recommend that your custom context menus include all common commands appropriate for your app.
</p>
</div>
</div>
<footer class="bg-white text-gray-900 border-t border-t-gray-200">
<div class="w-full max-w-screen-lg mx-auto px-4 py-6">
<p id="version" class="text-xs text-gray-900 m-0"></p>
<div class="text-sm px-0 mb-4 grid grid-cols-2 sm:grid-cols-3 gap-y-10">
<div>
<h2 class="text-base font-semibold text-nwoods-primary">GoJS</h2>
<ul class="list-none space-y-4 md:space-y-1 px-0">
<li>
<a href="../samples/index.html">Samples</a>
</li>
<li>
<a href="../learn/index.html">Learn</a>
</li>
<li>
<a href="../intro/index.html">Intro</a>
</li>
<li>
<a href="../api/index.html">API</a>
</li>
<li>
<a href="../changelog.html">Changelog</a>
</li>
<li>
<a href="https://github.com/NorthwoodsSoftware/GoJS" target="_blank" rel="noopener">GitHub</a>
</li>
</ul>
</div>
<div>
<h2 class="text-base font-semibold text-nwoods-primary">Support</h2>
<ul class="list-none space-y-4 md:space-y-1 px-0">
<li>
<a href="https://nwoods.com/contact.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/contact.html', 'contact');">Contact</a>
</li>
<li>
<a href="https://forum.nwoods.com/c/gojs" target="_blank" rel="noopener">Forum</a>
</li>
<li>
<a href="https://nwoods.com/app/activate.aspx?sku=gojs" target="_blank" rel="noopener">Activate</a>
</li>
<li>
<a href="https://nwoods.com/sales/index.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/sales/index.html', 'buy');">Buy</a>
</li>
<li>
<a href="https://nwoods.com/register.html" target="_blank" rel="noopener">Register</a>
</li>
</ul>
</div>
<div>
<h2 class="text-base font-semibold text-nwoods-primary">Company</h2>
<ul class="list-none space-y-4 md:space-y-1 px-0">
<li>
<a target="_blank" href="https://nwoods.com" target="_blank" rel="noopener">Northwoods</a>
</li>
<li>
<a target="_blank" href="https://nwoods.com/about.html" target="_blank" rel="noopener">About Us</a>
</li>
<li>
<a target="_blank" href="https://nwoods.com/contact.html" target="_blank" rel="noopener">Contact Us</a>
</li>
<li>
<a target="_blank" href="https://nwoods.com/consulting.html" target="_blank" rel="noopener">Consulting</a>
</li>
<li>
<a target="_blank" href="https://twitter.com/northwoodsgo" target="_blank" rel="noopener">Twitter</a>
</li>
</ul>
</div>
</div>
<p class="text-sm text-gray-900 md:mb-6">
Copyright 1998-2025 <a href="https://nwoods.com">Northwoods Software</a>
</p>
</div>
</footer>
</body>
<script async src="https://www.googletagmanager.com/gtag/js?id=G-S5QK8VSK84"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-S5QK8VSK84');
var getOutboundLink = function (url, label) {
gtag('event', 'click', {
'event_category': 'outbound',
'event_label': label,
'transport_type': 'beacon'
});
}
const params = new URL(document.location).searchParams
let a = params.get('a');
if (a) localStorage.setItem('a', a);
a = localStorage.getItem('a');
if (a) {
const links = [...document.body.getElementsByTagName("a")].filter((l) => l.href.includes('nwoods.com'));
for (const l of links) {
const url = new URL(l.href);
url.searchParams.set('a', a);
l.href = url;
}
}
</script>
<script src="../assets/js/prism.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gojs@3.1.0"></script>
<script src="../assets/js/goDoc.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
if (window.go)
document
.getElementById('version')
.textContent = "GoJS version " + go.version;
if (window.goDoc)
window.goDoc();
var d = window.diagrams;
for (var i = 0; i < d.length; i++) {
var dargs = d[i];
goCodeExecute(dargs[0], dargs[1], dargs[2], dargs[3], dargs[4]);
}
if (window.extra)
window.extra();
}
);
</script>
</html>