How to configure Context Menus
Context menus in GLSP are menu actions triggered in the conext of a selected element or a the diagram plane. Conext menues are tightly coupled to the execution platform. For example in Theia the context menu is shown by a right-mouse-click on the diagram plane.

In Visual-Code the same menu action is handled as a so called sub-menu which appears on the top bar of the diagram.

For this reason the implementation is different for both platforms. To add custom menu elements in the diagram context menu the following parts need to be implemented
Define Your Own Action
Before you get started, you'll need to define your custom Action class (unless you're using one of the GLSP default actions).
export interface MyAction extends Action {
kind: typeof MyAction.KIND;
}
export namespace MyAction {
export const KIND = 'properties';
export function is(object: any): object is MyAction {
return Action.hasKind(object, KIND);
}
export function create(): MyAction {
return { kind: KIND };
}
}
Correspondign to your action you also define a ActionHandler class
@injectable()
export class MyActionHandler implements IActionHandler {
handle(action: MyAction): void | MyAction {
console.log('--------> custom action arrived');
// implement your custom logic to handle the action
// Optionally issue a response action
}
}
… and register the handler in your DiagramModule:
const bpmnDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => {
const context = { bind, unbind, isBound, rebind };
....
// Action BPMN Property Action Handler
configureActionHandler(context, MyAction.KIND, MyActionHandler);
...
});
Next you can define a new Context Menu with you own Custom Action class.
Context Menu in Theia Module
To add a new context menu entry in the Theia platform you first need to implement a command and menu contribution class within your Theia-client-part. This can be done in a separate module. This module is exporting two classes:
- a
CommandContribution
defining a command action to be send - a
MenuContribution
defining the context menu entries
In the CommandContribution
it is also possible to define in which situation the context mene should be enabled or disabled. For example you can verify if an element is selected or not. The MenuContribution
defines the name and path of the menu entry.
export namespace MyCustomCommands {
export const OPEN_ACTION = 'glsp-my-open-action';
}
@injectable()
export class MyCommandContribution implements CommandContribution {
@inject(ApplicationShell) protected readonly shell: ApplicationShell;
registerCommands(commands: CommandRegistry): void {
// register commands...
commands.registerCommand(
{ id: MyCustomCommands.OPEN_ACTION, label: 'Open Properties' },
new GLSPCommandHandler(this.shell, {
actions: () => [MyAction.create()],
isEnabled: context => context.selectedElements.length === 1
})
);
}
}
@injectable()
export class MyMenuContribution implements MenuContribution {
registerMenus(menus: MenuModelRegistry): void {
menus.registerMenuAction(GLSPContextMenu.MENU_PATH.concat('z'), {
commandId: MyCustomCommands.OPEN_ACTION,
label: 'Open Properties'
});
}
}
Next the contributions need to be bound in TheiaFrontendModule
of the webview. This can be done by overwriting the configure(context)
method. Here is an example:
...
override configure(context: ContainerContext): void {
context.bind(CommandContribution).to(MyCommandContribution);
context.bind(MenuContribution).to(MyMenuContribution);
}
...
Now whenever the user opens the context menu in the diagram plane the new entry will appear and fires the corresponding command.
Context Menu in Visual Code
In Visual Code no context menu exits but the so called sub menu which appears on top of the diagram plane.
To add a new command contribution to a VisualCode extension the commands can be registered within the activate
function of the extension module:
....
configureDefaultCommands({
extensionContext: context,
connector: glspVscodeConnector,
diagramPrefix: "bpmn",
});
context.subscriptions.push(
vscode.commands.registerCommand("bpmn.showProperties", () => {
console.log("send show properties...");
glspVscodeConnector.sendActionToActiveClient(
MyAction.create("properties")
);
})
);
...
Also here the command is responsible for sending the corresponding action event.
Next you can register the sub menu entries in the extension configuration in the file package.json
within the contributes
section:
....
"contributes": {
....
"commands": [
{
"command": "bpmn.showProperties",
"title": "Show Properties...",
"category": "BPMN Diagram",
"enablement": "activeCustomEditorId == 'bpmn-diagram' && bpmn.editorSelectedElementsAmount == 1"
},
{
"command": "bpmn.closeProperties",
"title": "Close Properties...",
"category": "BPMN Diagram",
"enablement": "activeCustomEditorId == 'bpmn-diagram' && bpmn.editorSelectedElementsAmount == 1"
}
],
"submenus": [
{
"id": "bpmn.editor.title",
"label": "Diagram"
}
],
"menus": {
"editor/title": [
{
"submenu": "bpmn.editor.title",
"group": "bookmarks"
},
{
"command": "bpmn.showProperties",
"group": "navigation",
"when": "activeCustomEditorId == 'bpmn-diagram' && bpmn.editorSelectedElementsAmount == 1"
},
{
"command": "bpmn.closeProperties",
"group": "navigation",
"when": "activeCustomEditorId == 'bpmn-diagram' && bpmn.editorSelectedElementsAmount == 1"
}
],
....
Note: To integrate GLSP Actions into the VSCode you need to define the Action class again in your extension module. We do this in the file open-bpmn-actions.ts
. For some reason it is not possible to import this class from a other package.
The NaviateAction
The context menu command is a generic API for handling all context menu actions. One special action type, provided by the GLSP API is the NaviateAction
which is tailored for specific use cases.
The NaviateAction
can be used to implement complex navigation like Go to next Node, Go to reference, Go to source etc. where the client does not have all the necessary information to navigate to the correct target. In this cases your menu command can trigger a NavigateAction
with a specific navigation target. The action is send to the server which is responsible for resolving the target and sends back the necessary information so that the client can actually navigate to this target. You can find a good example in the GLSP Workflow Example.
To register this kind of action on the client use the class NavigateAction
which specifies the target.
...
import { NavigateAction } from '@eclipse-glsp/client';
...
commands.registerCommand(
{ id: PropertyPanelCommands.PROPERTIES_OPEN, label: 'Open Documentation' },
new GLSPCommandHandler(this.shell, {
actions: () => [NavigateAction.create('documentation')],
isEnabled: context => context.selectedElements.length === 1
})
);
On the server this action will result in a RequestNavigationTargetsAction
(see NavigationActionHandler) in order to request the navigation targets from the server.
public class PropertiesNavigationTargetProvider implements NavigationTargetProvider {
@Override
public String getTargetTypeId() {
return "documentation";
}
@Override
public List<? extends NavigationTarget> getTargets(final EditorContext editorContext) {
// your code goes here...
return Arrays.asList();
}
}
Adn in your DiagramModule you need again bind your Provider class
....
@Override
protected void configureNavigationTargetProviders(final MultiBinding<NavigationTargetProvider> binding) {
super.configureNavigationTargetProviders(binding);
binding.add(PropertiesNavigationTargetProvider.class);
}
...