Ah, if you don’t want it to act like a context menu, where a click executes some command and the context menu goes away, you don’t want to use a context menu (i.e. GraphObject.contextMenu and the ContextMenuTool).
OK, just use an Adornment without using any context menu stuff. There’s more to implement that way, but it isn’t too complicated.
<!DOCTYPE html>
<html>
<head>
<title>Button Click Shows Choices Menu</title>
<!-- Copyright 1998-2024 by Northwoods Software Corporation. -->
<meta name="description" content="A button in a Node when clicked shows an Adornment for the node." />
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="myDiagramDiv" style="border: solid 1px black; width:400px; height:400px"></div>
<textarea id="mySavedModel" style="width:100%;height:250px"></textarea>
<script src="https://unpkg.com/gojs"></script>
<script id="code">
const $ = go.GraphObject.make; // for conciseness in defining templates
myDiagram = new go.Diagram("myDiagramDiv", // create a Diagram for the DIV HTML element
{
"undoManager.isEnabled": true, // enable undo & redo
"ModelChanged": e => { // just for demonstration purposes,
if (e.isTransactionFinished) { // show the model data in the page's TextArea
document.getElementById("mySavedModel").textContent = e.model.toJson();
}
}
});
const ChoicesMenu =
$(go.Adornment, "Spot",
$(go.Placeholder),
$(go.Panel, "Vertical",
new go.Binding("itemArray", "applications"),
{
background: "whitesmoke",
alignment: go.Spot.BottomRight,
alignmentFocus: go.Spot.TopRight,
defaultAlignment: go.Spot.Left,
itemTemplate:
$("CheckBox", "selected",
{
margin: new go.Margin(10, 10, 10, 10),
"ButtonBorder.spot1": go.Spot.TopLeft, "ButtonBorder.spot2": go.Spot.BottomRight
},
$(go.TextBlock,
{ margin: new go.Margin(0, 0, 0, 10) },
new go.Binding("text", "label"))
)
}
)
);
// define a simple Node template
myDiagram.nodeTemplate =
$(go.Node, "Auto", // the Shape will go around the TextBlock
$(go.Shape, { strokeWidth: 0 },
// Shape.fill is bound to Node.data.color
new go.Binding("fill", "color")),
$(go.Panel, "Horizontal",
{ margin: new go.Margin(8, 0, 8, 8) }, // some room around the text & button
$(go.TextBlock,
// TextBlock.text is bound to Node.data.key
new go.Binding("text", "key")),
$(go.Shape, "TriangleDown",
{
name: "TriangleButton",
width: 10, height: 6, margin: 4,
background: "transparent",
click: (e, shape) => {
const node = shape.part;
if (ChoicesMenu.adornedObject && ChoicesMenu.adornedObject !== node) {
showChoicesList(ChoicesMenu.adornedObject, false);
}
showChoicesList(node, ChoicesMenu.adornedObject === null);
}
})
)
);
// show or hide the choices list for the given node
function showChoicesList(node, show) {
const shape = node.findObject("TriangleButton");
if (shape) {
node.diagram.commit(diag => shape.figure = show ? "TriangleUp" : "TriangleDown",
null); // skipsUndoManager
}
if (show) {
ChoicesMenu.adornedObject = node;
node.addAdornment("Choices", ChoicesMenu);
} else {
node.removeAdornment("Choices");
ChoicesMenu.adornedObject = null;
}
}
// but use the default Link template, by not setting Diagram.linkTemplate
// create the model data that will be represented by Nodes and Links
myDiagram.model = new go.GraphLinksModel(
[
{ key: "Alpha", color: "lightblue", applications:
[
{
label: "Application one one",
selected: false,
},
{
label: "Application two",
selected: true,
},
{
label: "Application three",
//selected: true,
},
]
},
{ key: "Beta", color: "orange", applications:
[
{
label: "Application 1",
selected: true,
},
{
label: "Application 2",
selected: true,
},
]
}
],
[
{ from: "Alpha", to: "Beta" },
]);
</script>
</body>
</html>