How to create a custom ContextMenu


#1

I created a custom adornment menu using the the defineBuilder with the objective to have buttons with the highlight disabled as you can see here:

 go.GraphObject.defineBuilder("ButtonWithoutHighlight", function(args) {
          const { name } = args[1]

          let button = $("Button", makeGObject(name))

          let border = button.findObject("ButtonBorder")
          if (border instanceof go.Shape) {
            border.stroke = null
            border.fill = "transparent"
          }

          button.mouseEnter = function(e, button, prev) {
            let shape = button.findObject("ButtonBorder")
            if (shape instanceof go.Shape) {
              border.stroke = null
              border.fill = "transparent"
            }
          }

          button.mouseLeave = function(e, button, prev) {
            let shape = button.findObject("ButtonBorder")
            if (shape instanceof go.Shape) {
              border.stroke = null
              border.fill = "transparent"
            }
          }

          return button
        })

        const makeGObject = function(name) {
          let SvgIcon

          switch (name) {
            case "map":
              SvgIcon = go.Geometry.parse(mapPath, true)
              break

            default:
              return $(go.TextBlock, name, {
                font: "bold 14pt sans-serif",
                stroke: "#fff",
                desiredSize: new go.Size(16, 16),
                textAlign: "center",
              })
          }

          const gObject = $(go.Shape, {
            column: 0,
            width: 16,
            height: 16,
            margin: 2,
            fill: "#fff",
            strokeWidth: 0,
            geometry: SvgIcon,
          })

          return gObject
        }

let $upperContextMenu = $(
      go.Panel,
      "Horizontal",

      $("ButtonWithoutHighlight", {
        name: "map",
        click: (e, obj) => {
          const contextmenu = obj.part
          const node = contextmenu.data
          console.log("aaaa", node)
        },
      }),

      $("ButtonWithoutHighlight", {
        name: "map",
        contextMenu: $(
          go.Adornment,
          "Vertical",
          new go.Binding("itemArray", "NavigationMaps"),
          {
            itemTemplate: $(
              "ContextMenuButton",
              $(
                go.TextBlock,
                {
                  stroke: "#fff",
                  background: IrionBlue,
                  textAlign: "center",
                  margin: 2,
                },
                new go.Binding("text", "NavigationMapName")
              ),
              {
                alignment: go.Spot.Right,
              }
            ),
          }
        ),

        click: (e, node) => e.diagram.commandHandler.showContextMenu(node),
      })
    )

    const adornmentMenu = $(
      go.Adornment,
      "Vertical",
      $(
        go.Panel,
        "Auto",
        { height: 18, alignment: new go.Spot(1, 0, -10, 0) },

        $(go.Shape, "RoundedTopRectangle", {
          fill: IrionBlue,
          stroke: IrionBlue,
        }),
        $upperContextMenu
      ),
      $(
        go.Panel,
        "Auto",
        $(go.Shape, "RoundedRectangle", {
          spot1: go.Spot.TopLeft,
          spot2: go.Spot.BottomRight,
          stroke: IrionBlue,
          strokeWidth: 2,
          fill: null,
        }),
        $(go.Placeholder, { margin: 1 })
      )
    )

Now I’d like to apply the same style at the buttons in the ContextMenu but this is the result.

navmap

Is it possible to apply the same style as I implemented in the “ButtonWithoutHighlight” for the ContextMenu buttons? I tried this way:

go.GraphObject.defineBuilder("ButtonNoHighlight", function(args) {
          const { name } = args[1]

          let button = $(
            "Button",
            $(go.TextBlock, name, {
              font: "bold 14pt sans-serif",
              stroke: "#fff",
              textAlign: "center",
            })
          )

          let border = button.findObject("ButtonBorder")
          if (border instanceof go.Shape) {
            border.stroke = null
            border.fill = "transparent"
          }

          button.mouseEnter = function(e, button, prev) {
            let shape = button.findObject("ButtonBorder")
            if (shape instanceof go.Shape) {
              border.stroke = null
              border.fill = "transparent"
            }
          }

          button.mouseLeave = function(e, button, prev) {
            let shape = button.findObject("ButtonBorder")
            if (shape instanceof go.Shape) {
              border.stroke = null
              border.fill = "transparent"
            }
          }

          return button
        })

and then I used

$("ButtonWithoutHighlight", {
            name: "map",
            contextMenu: $(
              go.Adornment,
              "Vertical",
              new go.Binding("itemArray", "NavigationMaps"),
              {
                itemTemplate: $(
                  "ContextMenuButton",
                  $("ButtonNoHighlight", {
                    name: "mymapelement",
                    click: (e, obj) => {},
                  })
                ),
              }
            ),

            click: (e, node) => e.diagram.commandHandler.showContextMenu(node),
          })

but the result doesn’t take the binding new go.Binding("text", "NavigationMapName")

navmap2

What do I have to do?


#2

Is “NavigationMapName” a property on each item in the Array? That’s what that Binding is using as the source.


#3

Yes “NavigationMapName” is a property, this is a sample of the object structure:

{ 
  ClosedNavigation: true,
  EntityTypeId: "08f368e4-dc49-4cda-b184-73313b3e2f15",
  EntityTypeName: "BG",
  NavigationMapDescription: "Mappa",
  NavigationMapId: "4672a733-1632-418b-90f0-c150076d11aa",
  NavigationMapName: "BGMap",
  ScopeName: "BG",
}

If I use something like this

$("ButtonWithoutHighlight", {
            name: "map",
            contextMenu: $(
              go.Adornment,
              "Vertical",
              new go.Binding("itemArray", "NavigationMaps"),
              {
                itemTemplate: $(
                  "ContextMenuButton",
                  $(
                    "BtnTest",
                    {
                      name: "empty",
                      click: (e, obj) => {
                        const contextmenu = obj.part
                        const node = contextmenu.data
                        console.log("aaaa", node)
                      },
                    },
                    new go.Binding("name", "NavigationMapName")
                  )
                ),
              }
            ),

            click: (e, node) => e.diagram.commandHandler.showContextMenu(node),
          })

go.GraphObject.defineBuilder("BtnTest", function(args) {
          const { name } = args[1]

          let button = $(
            "Button",
            $(go.TextBlock, name, {
              font: "bold 14pt sans-serif",
              stroke: "#fff",
              textAlign: "center",
            })
          )

          let border = button.findObject("ButtonBorder")
          if (border instanceof go.Shape) {
            border.stroke = null
            border.fill = "transparent"
          }

          button.mouseEnter = function(e, button, prev) {
            let shape = button.findObject("ButtonBorder")
            if (shape instanceof go.Shape) {
              border.stroke = null
              border.fill = "transparent"
            }
          }

          button.mouseLeave = function(e, button, prev) {
            let shape = button.findObject("ButtonBorder")
            if (shape instanceof go.Shape) {
              border.stroke = null
              border.fill = "transparent"
            }
          }

          return button
        })

this is the result and as you can see it seems that the binding and the style is completely ignored

navmap3


#4

Do you really want to have a button within the “ContextMenuButton”?
That’s what you are doing with:

$("ContextMenuButton", $("ButtonNoHighlight", . . .))

I don’t know what the problem might be with your original code, but in this latest code I do not think you can bind the GraphObject.name property – it’s supposed to remain constant.
And in any case, it would not change the text shown by any TextBlock.


#5

Here’s an example of customizing the "ContextMenuButton"s:

    myDiagram.nodeTemplate =
      $(go.Node, "Auto",
        $(go.Shape, { fill: "white" },
          new go.Binding("fill", "color")),
        $(go.TextBlock, { margin: 8 },
          new go.Binding("text")),
        {
          contextMenu:
            $("ContextMenu", "Table",
              new go.Binding("itemArray", "buttons"),
              {
                itemTemplate:
                  $(go.Panel, "TableRow",
                    $("ContextMenuButton",
                      $(go.TextBlock, 
                        { stroke: "white", font: "bold 12pt sans-serif" },
                        new go.Binding("text")),
                      {
                        click: function(e, button) {
                          alert(button.panel.data.text + ": " + button.panel.row);
                        },
                        "ButtonBorder.fill": "gray",
                        _buttonFillNormal: "gray", 
                        _buttonFillOver: "red"
                      }
                    )
                  )
              }
            )
        }
      );

    myDiagram.model = new go.GraphLinksModel(
      [
        {
          key: 1, text: "Alpha", color: "lightgreen",
          buttons: [
            { text: "first command" },
            { text: "second command" }
          ]
        }
      ]);

Read the definition of “Button” (and “ContextMenuButton”) in extensions/Buttons.js to see what properties it saves and uses.

Here’s what it looks like after context-clicking on the node and having the mouse over the second button:
image


#6

This $("ContextMenuButton", $("ButtonNoHighlight", . . .)) hasn’t any sense at all, thank you for letting me know…

And many thanks for the following reply, I didn’t think about using a panel instead of create a new object definition with the builder.

Thanks