Keyboard navigation in context menu

As part of our need to certify for accessibility, we need to provide keyboard navigation for the context menu.

We have most of the rest of our diagram accessible but for this part. When in the context menu we can’t even seem to capture keydown events (in our extended DrawCommandHandler).

Is there any support for this, or even an extracted ContextMenuTool that we can extend.

Thanks
Alain

Normally the management of a context menu is performed by the ContextMenuTool, not the CommandHandler, so you need to override ContextMenuTool.doKeyDown.

Take a look at More Context Menu Behaviors. As usual, the complete source code is in the page itself.

Thanks Walter, this is helping me move forward. I am now able to use key up/down to select menu items.

One issue that I have here (and in another case as well) has to do with bindings. We’ve been using GoJS for a long time (still on 1.7.27), but I wasn’t the one managing this before. The context menu already contained bindings to change disabled menu items but that was/isn’t working. The opacity bindings were there and I added the background but they are not getting called. The same enablement function works fine on mouseEnter for example.

function createContextMenuItem(pictureSource, label, tooltip, action, enablementFunction) {
	return $g(go.Panel, go.Panel.Auto,
	{
		click : function(e, obj) {
			if (isEnabled(obj.part.data, enablementFunction)) {
				action(e, obj);
			}
		},
		desiredSize : new go.Size(200, 25),
		mouseEnter: function(e, menuItem, prev) {
			if (isEnabled(menuItem.part.data, enablementFunction)) {
				var shape = menuItem.findObject("mainShape");
				shape.fill = "rgb(243,243,243)";
				return true;
			}
			return false;
		},
		mouseLeave: function(e, menuItem, prev) {
			if (isEnabled(menuItem.part.data, enablementFunction)) {
				var shape = menuItem.findObject("mainShape");
				shape.fill = "white";
				return true;
			}
			return false;
		},
		toolTip: createToolTip(tooltip)
	},
			$g(go.Shape, "Rectangle", {
				name: "mainShape",
				// background: "white",
				fill: "white",
				stroke: "lightgray"
			},
				getShadowVisibleBinding(),
				new go.Binding("background", "", function(obj) {
					return isEnabled(obj.part.data, enablementFunction) ? white : "rgb(197,197,197)";
				}).ofObject()
			),
			$g(go.Picture,
			{
				width : 16,
				height : 16,
				margin : new go.Margin(3, 2, 5, 3),
				alignment : go.Spot.MiddleLeft,
				imageStretch : go.GraphObject.Uniform,
				source : pictureSource,
				shadowVisible: false
			},
			new go.Binding("opacity", "", function(obj) {
				return isEnabled(obj.part.data, enablementFunction) ? 1 : 0.3;
			}).ofObject()
			),
			$g(go.TextBlock, label,
			{
				name: "label",
				textAlign : "left",
				isMultiline: true,
				alignment : go.Spot.MiddleLeft,
				margin : new go.Margin(0, 2, 0, 25),
				font: "13px Arial, sans-serif",
				shadowVisible: false
			},
			new go.Binding("opacity", "", function(obj) {
				return isEnabled(obj.part.data, enablementFunction) ? 1 : 0.3;
			}).ofObject()
			)
	);
}

My other binding issue has to do with wanting to change the selection adornment style. I am using the DrawCommandHelper in select mode and want to show the focus node differently than the selected ones when doing multi-select (btw fixed to allow tracking last selected). Debugging I can see that transaction is called correctly but the node loosing the focus doesn’t gets updated, but the one gaining the focus is fine. Just added updateTargetBindings and same results.

function updateCurrentSelection(entering) {
	runInTransaction("Update current selection", true, function() {
		var model = mainDiagram.model;
		if (model.modelData._lastSelection !== undefined) {
			model.setDataProperty(model.modelData._lastSelection.data, "_selectionDashArray", null);
      model.modelData._lastSelection.updateTargetBindings("_selectionDashArray");
		}
		if (entering !== undefined) {
			model.setDataProperty(entering.data, "_selectionDashArray", currentSelectionDashArray);
		}
		model.modelData._lastSelection = entering;
	});
}

and the template with binding:

var selectionTemplate =
$g(go.Adornment, "Auto",
	$g(go.Shape, "RoundedRectangle",
		{
			fill: null,
			stroke: "dodgerblue",
			strokeWidth: 3
		},
		new go.Binding("strokeDashArray", "_selectionDashArray").makeTwoWay()),
		$g(go.Placeholder)
);

Version 1.7.27 was from six years ago, so it’s hard for us to know what might be different that would help you. We haven’t had to support any v1.7.* for quite a while.

I’m not sure what the question is or how I could reproduce the situation you have. I suggest that you step through the binding conversion functions to see why they aren’t working the way that you expect.

BTW, the GraphObject.mouseEnter and mouseLeave event handlers don’t return any value. GraphObject | GoJS API

Walter,
Our version is very old, but as they say, if it ain’t broke don’t fix it. Would like to update, but afraid of facing number of issues w/o any experience putting it together.

Ok, I have been able to fix the 1st binding issue. From some note in another context menu, it was indicated that .copy() would break bindings and here the full menu was a copy, so avoiding that fixed the issue. BTW, the return values were added since those functions are now called from the custom context menu tool for keyboard navigation.

Now with the 2nd binding issue, I can’t get it to work after trying multiple variants. Is there anything special with bindings on adornment like here for the selectionAdornmentTemplate ? I even tried to call

part.selectionAdornmentTemplate.updateTargetBindings("_selectionDashArray");

and then I get this error: Binding error: missing GraphObject named null in Adornment() and in debug, which is almost impossible even with go-debug-readable, I can see that it is dealing with the binding object, but not clear what it is trying to iterate over.

Thanks

Templates are Parts (or Panels, for item templates) that must not be in the Diagram and must not have any data. So it doesn’t make sense to try to evaluate any bindings that are in templates. One can only evaluate bindings in Parts that are in Diagrams.

Also, you must be careful that there are never any references to any GraphObjects or Tools or the Diagram in the model. The whole point of models is that they are just plain JavaScript values (Object, Arrays, numbers, strings, booleans) so that they can be serialized in JSON format or whatever you want.

I’ll create a sample that shows how to have a different appearance and/or selection adornment for the primary selection than for other selected parts.

I hope this demonstrates something like what you are looking for:

The first selected node gets a magenta color; other selected nodes get a cyan color.

Each selected node has one or two buttons; only the primary selected node has a magenta circle button.

image

The code had been simpler, but then I realized that the first selected Part might be a Link instead of a Node, so I added a function to find the first selected Node.

As always, the complete source code is in the page itself.

Thanks, simply adding:

part.updateAdornments();

to my code fixed the issue, that was the secret sauce that was missing here.