Context menus

A context menu contains a list of actions that a user can perform on a workspace, block, or workspace comment. The context menu is shown in response to a right-click, or a long press on a touch device.

The default context menu for a block

Context menus are a good place to add actions that the user performs infrequently, such as downloading a screenshot. If you think an action will be used more commonly, you might want to create a more discoverable way to invoke it.

There are separate context menus for workspaces, blocks, and workspace comments, each of which you can customize. You can also customize context menus on a per-block or per-workspace basis.

How context menus work

Blockly has a registry that contains menu item templates. Each template describes how to construct a single item in a context menu. When the user invokes a context menu, Blockly:

  1. Goes through all of the templates in the registry and decides which ones apply to the component on which the context menu was invoked (workspace, block, or workspace comment).

  2. Uses these templates to construct a corresponding array of menu items.

  3. If the context menu was invoked on a workspace or block, checks to see if that workspace or block has a function for customizing the context menu. If so, it passes the array to the function, which can add, delete, or modify elements of the array.

  4. Displays the context menu using the (possibly modified) array of context menu items.

The registry is preloaded with a standard set of templates for constructing context menus for workspaces and blocks. You can add, delete, or modify these templates. Blockly also has predefined templates for items that apply to workspace comments, but you must explicitly add these to the registry.

The RegistryItem interface

All templates in the registry implement the ContextMenuRegistry.RegistryItem interface. It contains the following properties.

ID

The id property should be a unique string that indicates what your context menu item does.

const deleteTemplate = {
  id: 'deleteElement',
  // ...
};

Scope type

The scopeType property tells Blockly what context the menu item can be shown in and what component to pass to its displayText, preconditionFn, and callback functions. Context menus can be scoped to blocks, workspace comments, and workspaces.

const deleteTemplate = {
  // ...
  scopeType: Blockly.ContextMenuRegistry.ScopeType.BLOCK,
  // ...
};

If you want a template to apply to multiple scopes, duplicate the template and redefine the scopeType property for each scope.

const otherDeleteTemplate = {...deleteTemplate};
otherDeleteTemplate.scopeType = Blockly.ContextMenuRegistry.ScopeType.COMMENT;

Display text

The displayText is what should be shown to the user as part of the menu item. The display text can be a string, or HTML, or a function that returns either a string or HTML.

const deleteTemplate = {
  // ...
  displayText: 'Delete block',
  // ...
};

If you want to display a translation from Blockly.Msg you need to use a function. If you try to assign the value directly, the messages may not be loaded and you'll get a value of undefined instead.

const deleteTemplate = {
  // ...
  displayText: () => Blockly.Msg['MY_DELETE_ITEM_TEXT'],
  // ...
};

If you use a function, it is also passed a Scope value which gives you access to the element the context menu is associated with (e.g. the block or workspace). You can use this to add information about the element to your display text.

const deleteTemplate = {
  // ...
  displayText: (scope) => `Delete ${scope.block.type} block`,
  // ...
}

Weight

The weight determines the order in which context menu items are displayed. More positive values are displayed lower in the list than less positive values. (You can imagine that items with higher weights are "heavier" so they sink to the bottom.)

const deleteTemplate = {
  // ...
  weight: 10,
  // ...
}

Weights for the built-in context menu items go in increasing order starting at 1 and increasing by 1.

Precondition function

In addition to the scopeType, you can use the preconditionFn to restrict when and how a context menu item should be displayed.

It should return one of a set of strings: 'enabled', 'disabled', or 'hidden'.

Value Description Image
enabled Shows that the item is active. An enabled option
disabled Shows that the item is not active. A disabled option
hidden Hides the item.

The preconditionFn is also passed a Scope which you can use to determine the state of your item.

const deleteTemplate = {
  // ...
  preconditionFn: (scope) => {
    if (scope.block.isDeletable()) {
      return 'enabled';
    }
    return 'disabled';
  },
  // ...
}

Callback function

The callback property is a function that performs the action of your context menu item. It is passed a Scope and a PointerEvent. The PointerEvent is the original event that triggered opening the context menu, not the one that selected an item. You can use the event to figure out where the click happened. This lets you, for example, create a new block at the click location.

const deleteTemplate = {
  // ...
  callback: (scope, e) => {
    scope.block.dispose();
  },
}

Customize the registry

You can add, delete, or modify templates in the registry.

Add a template

You can add a template to the registry by registering it. You should do this once on page load. It can happen before or after you inject your workspace.

const deleteTemplate = { /* properties from above */ };
Blockly.ContextMenuRegistry.registry.register(deleteTemplate);

Delete a template

You can remove a template from the registry by unregistering it by ID.

Blockly.ContextMenuRegistry.registry.unregister('someID');

The IDs for the default templates are in contextmenu_items.ts.

Modify a template

You can modify an existing template by getting the template from the registry and then modifying it in-place.

const template = Blockly.ContextMenuRegistry.registry.getItem('someID');
template?.displayText = 'some other display text';

Disable block context menus

By default, blocks have a context menu that allows users to do things like add comments or duplicate blocks.

You can disable the context menu of an individual block by doing:

block.contextMenu = false;

In the JSON definition of a block type, use the enableContextMenu key:

{
  // ...,
  "enableContextMenu": false,
}

Customize context menus per block type or workspace

After Blockly has generated a context menu, individual blocks or workspaces can customize it. To do this, define a function as the value of the customContextMenu property (blocks) or the configureContextMenu property (workspaces). The signatures of these functions are slightly different for blocks and workspaces, but both accept an array of objects that describe the items in the context menu. The function can modify the array (add, delete, or modify elements) in place.

The objects passed to blocks implement the ContextMenuOption interface or the LegacyContextMenuOption interface. The objects passed to workspaces implement ContextMenuOption.

ContextMenuOption has the following properties:

  • text is the display text.
  • enabled is a boolean. When disabled, the item is shown but with grey text.
  • callback is the function to be called when the item is clicked.
  • scope is a Scope object, which contains the block or workspace the context menu was invoked on.
  • weight determines where the item is displayed in the menu.

For example, here is a function that adds a Hello, World! item to a workspace's context menu:

workspace.configureContextMenu = function (menuOptions, e) {
  const item = {
    text: 'Hello, World!',
    enabled: true,
    callback: function () {
      alert('Hello, World!');
    },
    weight: 100,
  };
  menuOptions.push(item);
}