Selection dropdown menu

image
(the pointer is over the “Gamma” choice)

Of course you’ll want to style this the way that you want…

<!DOCTYPE html>
<html>
<head>
  <title>Simple Choices Selector Adornment</title>
  <!-- Copyright 1998-2024 by Northwoods Software Corporation. -->
</head>
<body>
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:400px"></div>
  <textarea id="mySavedModel" style="width:100%;height:150px"></textarea>

  <script src="https://unpkg.com/gojs"></script>
  <script id="code">
const myDiagram =
  new go.Diagram("myDiagramDiv", {
      "BackgroundSingleClicked": e => showChoices(null),
      "undoManager.isEnabled": true,
      "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();
        }
      }
    });

// the Adornment holding the list of choices
const ChoicesAdornment =
  new go.Adornment("Spot")
    .add(
      new go.Placeholder(),  // placeholder for the TextBlock in the Node
      new go.Panel("Auto", { alignment: go.Spot.BottomLeft, alignmentFocus: go.Spot.TopLeft })
        .add(
          new go.Shape("RoundedRectangle", { fill: "white", stroke: "gray", strokeWidth: 2 }),
          new go.Panel("Vertical", {
              margin: 4,
              defaultStretch: go.Stretch.Horizontal,
              itemTemplate:
                new go.Panel({
                    isActionable: true,  // to allow a click event in an Adornment
                    click: (e, item) => {
                      const tb = item.part.adornedPart.findObject("TB");
                      if (!tb) return;
                      e.diagram.commit(diag => {
                        tb.text = item.data;
                        showChoices(null);
                      });
                    },
                    // for mouse-over highlighting
                    background: "transparent",
                    mouseEnter: (e, item) => item.background = "cyan",
                    mouseLeave: (e, item) => item.background = "transparent"
                  })
                  .add(
                    new go.TextBlock({ margin: 1 })
                      .bind("text", "")  // TextBlock.text gets the whole Array item value
                  )
            })
            .bind("itemArray", "choices")
          )
      );

function showChoices(node) {
  myDiagram.commit(diag => {
    if (!node) {
      const oldnode = ChoicesAdornment.adornedPart;
      if (oldnode) {
        const oldshp = oldnode.findObject("SHP");
        if (oldshp) oldshp.figure = "LineDown";
        oldnode.removeAdornment("Choices");
      }
    } else {
      const tb = node.findObject("TB");
      const shp = node.findObject("SHP");
      if (!tb || !shp) return;
      showChoices(null);  // remove from any other node
      shp.figure = "LineUp";
      const ad = ChoicesAdornment;
      ad.adornedObject = tb;
      node.addAdornment("Choices", ad);
      if (!Array.isArray(node.data.choices) || node.data.choices.length === 0) {
        ad.data = { choices: ["Alpha", "Beta", "Gamma", "Delta"] };  // default choices Array
      }
    }
  }, null);  // skipsUndoManager
}

myDiagram.nodeTemplate =
  new go.Node("Auto")
    .add(
      new go.Shape({ fill: "white" }),
      new go.Panel("Vertical")
        .add(
          new go.TextBlock({ margin: 4, font: "bold 12pt sans-serif" })
            .bind("text", "title"),
          new go.Panel("Horizontal", {
              click: (e, pnl) => {  // show or hide the ChoicesAdornment
                e.handled = true;
                showChoices((pnl.findObject("SHP").figure === "LineDown") ? pnl.part : null);
              },
              // for mouse-over highlighting
              background: "transparent",
              mouseEnter: (e, pnl) => pnl.background = "lightgray",
              mouseLeave: (e, pnl) => pnl.background = "transparent"
            })
            .add(
              new go.TextBlock("(choose)", {
                  name: "TB",
                  width: 100,
                  margin: new go.Margin(4, 4, 2, 4),
                  font: "italic 10pt sans-serif",
                  stroke: "blue"
                })
                .bindTwoWay("text", "value")
                .bind("font", "value", v => v ? "bold 10pt sans-serif" : "italic 10pt sans-serif")
                .bind("stroke", "value", v => v ? "black" : "blue"),
              new go.Shape("LineDown", {
                name: "SHP",
                width: 14, height: 12,
                margin: 2,
                strokeWidth: 2,
              })
            )
        )
    );

myDiagram.model = new go.GraphLinksModel(
[
  { key: 1, title: "Alpha", choices: ["one", "two", "three"], value: "one" },
  { key: 2, title: "Beta", choices: ["hello", "goodbye"] },
  { key: 3, title: "Gamma", choices: ["only"] },
  { key: 4, title: "Delta" },  // use a default choices Array
]);
  </script>
</body>
</html>