ContextMenuButton color stuck

I created a ContextMenu object with ContextMenuButtons for undo and redo. I bound isEnabled to functions calling diagram.commandHandler.canUndo()/canRedo().

Now, I discovered that as soon as I click the undo/redo ContextMenuButton once, it afterwards gets stuck with its _buttonFillOver color. It still does change to _buttonFillDisabled when it’s disabled, but never changes back to _buttonFillNormal again. Am I doing something wrong here?

I also had a look into Buttons.js and was thinking that maybe btn[‘_buttonFillNormal’] gets messed up at some point when shape.fill is assigned to it but still may contain the value of btn['_buttonFillOver]?

Here is how I define the buttons:

function getContextMenuButton(text, onClick, isEnabled) {
    return go.GraphObject.build("ContextMenuButton", {click: onClick, "_buttonFillDisabled": COLOR_LIGHTGREY})
                .bindObject("isEnabled", "", isEnabled)
                .bindObject("cursor", "", (o) => isEnabled(o) ? "pointer" : "auto")
                .add(new go.TextBlock(text, {width: CONTEXT_MENU_W, textAlign: "center"})
                        .bindObject("stroke", "", (o) => isEnabled(o) ? COLOR_BLACK: COLOR_GREY));
}

function isUndoEnabled(o) {
    if ((o !== null) && (o.diagram !== null)) {
        return o.diagram.commandHandler.canUndo();
    }
    return false;
}

function isRedoEnabled(o) {
    if ((o !== null) && (o.diagram !== null)) {
        return o.diagram.commandHandler.canRedo();
    }
    return false;
}

const undoButton = getContextMenuButton("Undo", (e, o) => e.diagram.commandHandler.undo(), isUndoEnabled);
const redoButton = getContextMenuButton("Redo", (e, o) => e.diagram.commandHandler.redo(), isRedoEnabled);

I’m not sure what your code is doing. When I try the context menu Undo and Redo buttons in the Basic sample, Basic Sample Showing ToolTips and Context Menus for Nodes, Links, Groups, and Diagram | GoJS Diagramming Library, I find that the buttons work well.

Here’s how they are implemented:

        makeButton(
          'Undo',
          (e, obj) => e.diagram.commandHandler.undo(),
          (o) => o.diagram.commandHandler.canUndo()
        ),
        makeButton(
          'Redo',
          (e, obj) => e.diagram.commandHandler.redo(),
          (o) => o.diagram.commandHandler.canRedo()
        ),

where makeButton is defined as:

    // To simplify this code we define a function for creating a context menu button:
    function makeButton(text, action, visiblePredicate) {
      let button = go.GraphObject.build('ContextMenuButton')
        .add(
          new go.TextBlock(text, { click: action })
        );
      // don't bother with binding GraphObject.visible if there's no predicate
      if (visiblePredicate)
        button.bindObject('visible', '', (o, e) => (o.diagram ? visiblePredicate(o, e) : false));
      return button;
    }

I also tried customizing the button colors:

          go.GraphObject.build("ContextMenuButton", {
              _buttonFillNormal: "lime",
              _buttonFillOver: "cyan",
              _buttonFillDisabled: "magenta",
              click: (e, button) => e.diagram.commandHandler.undo()
            })
            .bindObject("isEnabled", "", ad => !!ad.diagram?.commandHandler.canUndo())
            .add(
              new go.TextBlock("Undo")
            ),
          go.GraphObject.build("ContextMenuButton", {
              _buttonFillNormal: "lime",
              _buttonFillOver: "cyan",
              _buttonFillDisabled: "magenta",
              click: (e, button) => e.diagram.commandHandler.redo()
            })
            .bindObject("isEnabled", "", ad => !!ad.diagram?.commandHandler.canRedo())
            .add(
              new go.TextBlock("Redo")
            )

What part of my code exactly are you not sure about? I try to give a more minimal example, based on yours. My makeButton function would look as follows then:

function makeContextMenuButton(text, action, enabledPredicate) {
    let button = go.GraphObject.build("ContextMenuButton", {
            _buttonFillNormal: "lime",
            _buttonFillOver: "cyan",
            _buttonFillDisabled: "magenta",
            click: action
        })
        .add(
            new go.TextBlock(text)
        );
    if (enabledPredicate) {
        button.bindObject("isEnabled", "", (o, e) => (o.diagram? enabledPredicate(o, e) : false))
    }
    return button;
}

The context menu is created with:

go.GraphObject.build("ContextMenu")
    .add(
        makeContextMenuButton(
            "Undo",
            (e, o) => e.diagram.commandHandler.undo(),
            (o) => o.diagram.commandHandler.canUndo()),
        makeContextMenuButton(
            "Redo",
            (e, o) => e.diagram.commandHandler.redo(),
            (o) => o.diagram.commandHandler.canRedo())
    );

My goal is to have a context menu where the buttons are always visible but only enabled when the respective action can be executed. On the functional side, the implementation is working as it should, but visually I face the following issues:

  1. In their normal state, the buttons do not appear in the set _buttonFillNormal (lime), but still in the respective default color (#f5f5f5).
    Buttons initial.
  2. Whenever I once clicked one of the buttons, it will afterwards just stay at the _buttonFillOver (cyan) color and never go back to normal state.
    Buttons undo clicked.

I see. We’re looking into this.

1 Like

OK, I believe we have improved the implementation of “Button” and all builders that use it for the 3.0.14 release, which should be available soon.

In general when customizing button colors, you need to set the initial “ButtonBorder.fill” or “ButtonBorder.stroke”. You can set the “_buttonFillNormal” or “_buttonStrokeNormal” too, but that will not be necessary in 3.0.14. (The other extra properties must be set if you want to change them.)