21 Nov 2022

Customizing Viewer Context Menus: Part 2

Couple years ago our colleague Eason blogged about customizing viewer context menus. In this blog post let's take a closer look at the specific capabilities of this customization.

As a quick reminder, context menus in the viewer are customized by registering so-called context menu callbacks.

viewer.registerContextMenuCallback('your-custom-callback-id', function (menu, status) {
    // Customize your menu here
});

// ... and later

viewer.unregisterContextMenuCallback('your-custom-callback-id');

One important thing to note here is that the callbacks are executed every time the context menu is about to open. This allows you to add dynamic behavior to your customized menu, for example, hiding/showing specific menu entries, or changing their text or appearance based on the current state of the viewer.

In their simplest form, context menu callbacks can simply add a new menu entry like so:

viewer.registerContextMenuCallback('your-custom-callback-id', function (menu, status) {
    menu.push({
        title: 'Say Hello World',
        target: function () { alert('Hello World!'); }
    });
});

The menu argument passed to the callback is an array of JavaScript objects representing individual menu entries. Each object can define the following properties:

  • title - menu entry title
  • target - can be one of the following:
    • function to execute when the menu entry is clicked
    • an array of JavaScript objects representing nested menu entries
  • icon - CSS class name for an optional icon in front of the menu entry title
  • shortcut - menu entry keyboard shortcut
    • note that this will only display the shortcut letter, not handle the actual keyboard events
  • divider - boolean flag turning this menu entry into a divider
viewer.registerContextMenuCallback('your-custom-callback-id', function (menu, status) {
    menu.push({
        title: 'My Actions',
        target: [
            {
                title: 'Say Hello World',
                icon: 'adsk-icon-pan',
                shortcut: 'h',
                target: function () { alert('Hello World!'); }
            },
            {
                divider: true
            },
            {
                title: 'Say Goodbye',
                icon: 'adsk-icon-plane-x',
                target: function () { alert('Goodbye!'); }
            }
        ]
    });
});

If you want to reuse some of the viewer icons as we did in the example above, please refer to https://forge.autodesk.com/blog/what-icons-are-provided-viewer-stylesheet for more details.

Finally, the status argument passed to your context menu callback is a JavaScript object with useful information about the current status of the viewer and the event that triggered the context menu itself. These are the properties available in the object:

  • event - browser event that requested the context menu
  • numSelected - number of objects selected in the viewer
  • hasSelected - boolean flag indicating whether any objects are selected
  • hasVisible - boolean flag indicating whether any of the selected objects are visible
  • hasHidden - boolean flag indicating whether any of the selected objects are hidden
  • canvasXcanvasY - X and Y coordinate of the mouse event that requested the context menu

Of course, you can always use the viewer APIs if this status object does not provide information you need:

viewer.registerContextMenuCallback('your-custom-callback-id', function (menu, status) {
    if (status.hasSelected) {
        menu.push({
            title: `Color ${status.numSelected} Selected Elements`,
            target: function () {
                const dbids = viewer.getSelection();
                const color = new THREE.Vector4(1, 0, 0, 0.5);
                for (const dbid of dbids) {
                    viewer.setThemingColor(dbid, color, undefined, true);
                }
                viewer.clearSelection();
            }
        });
    }
    menu.push({
        title: 'Clear Custom Color',
        target: function () {
            viewer.clearThemingColors();
        }
    });
});

 

Related Article