import {ContextMenu} from '@xeokit/xeokit-sdk/src/extras/ContextMenu/ContextMenu.js'
ContextMenu
A customizable HTML context menu.
Overview
- A pure JavaScript, lightweight context menu
- Dynamically configure menu items
- Dynamically enable or disable items
- Dynamically show or hide items
- Supports cascading sub-menus
- Configure custom style with CSS (see examples above)
Usage
In the example below we'll create a ContextMenu
that pops up whenever we right-click on an Entity within
our Scene.
First, we'll create the ContextMenu
, configuring it with a list of menu items.
Each item has:
- a
title
for the item, - a
doAction()
callback to fire when the item's title is clicked, - an optional
getShown()
callback that indicates if the item should shown in the menu or not, and - an optional
getEnabled()
callback that indicates if the item should be shown enabled in the menu or not.
The getShown()
and getEnabled()
callbacks are invoked whenever the menu is shown.
When an item's getShown()
callback
returns true
, then the item is shown. When it returns false
, then the item is hidden. An item without
a getShown()
callback is always shown.
When an item's getEnabled()
callback returns true
, then the item is enabled and clickable (as long as it's also shown). When it
returns false
, then the item is disabled and cannot be clicked. An item without a getEnabled()
callback is always enabled and clickable.
Note how the doAction()
, getShown()
and getEnabled()
callbacks accept a context
object. That must be set on the ContextMenu
before we're able to we show it. The context object can be anything. In this example,
we'll use the context object to provide the callbacks with the Entity that we right-clicked.
We'll also initially enable the ContextMenu
.
const canvasContextMenu = new ContextMenu({
enabled: true,
items: [
[
{
title: "Hide Object",
getEnabled: (context) => {
return context.entity.visible; // Can't hide entity if already hidden
},
doAction: function (context) {
context.entity.visible = false;
}
}
],
[
{
title: "Select Object",
getEnabled: (context) => {
return (!context.entity.selected); // Can't select an entity that's already selected
},
doAction: function (context) {
context.entity.selected = true;
}
}
],
[
{
title: "X-Ray Object",
getEnabled: (context) => {
return (!context.entity.xrayed); // Can't X-ray an entity that's already X-rayed
},
doAction: (context) => {
context.entity.xrayed = true;
}
}
]
]
});
Next, we'll make the ContextMenu
appear whenever we right-click on an Entity. Whenever we right-click
on the canvas, we'll attempt to pick the Entity at those mouse coordinates. If we succeed, we'll feed the
Entity into ContextMenu
via the context object, then show the ContextMenu
.
From there, each ContextMenu
item's getEnabled()
callback will be invoked (if provided), to determine if the item should
be enabled. If we click an item, its doAction()
callback will be invoked with our context object.
Remember that we must set the context on our ContextMenu
before we show it, otherwise it will log an error to the console,
and ignore our attempt to show it.
viewer.scene.canvas.canvas.oncontextmenu = (e) => { // Right-clicked on the canvas
if (!objectContextMenu.enabled) {
return;
}
var hit = viewer.scene.pick({ // Try to pick an Entity at the coordinates
canvasPos: [e.pageX, e.pageY]
});
if (hit) { // Picked an Entity
objectContextMenu.context = { // Feed entity to ContextMenu
entity: hit.entity
};
objectContextMenu.show(e.pageX, e.pageY); // Show the ContextMenu
}
e.preventDefault();
});
Note how we only show the ContextMenu
if it's enabled. We can use that mechanism to switch between multiple
ContextMenu
instances depending on what we clicked.
Dynamic Item Titles
To make an item dynamically regenerate its title text whenever we show the ContextMenu
, provide its title with a
getTitle()
callback. The callback will fire each time you show ContextMenu
, which will dynamically
set the item title text.
In the example below, we'll create a simple ContextMenu
that allows us to toggle the selection of an object
via its first item, which changes text depending on whether we are selecting or deselecting the object.
const canvasContextMenu = new ContextMenu({
enabled: true,
items: [
[
{
getTitle: (context) => {
return (!context.entity.selected) ? "Select" : "Undo Select";
},
doAction: function (context) {
context.entity.selected = !context.entity.selected;
}
},
{
title: "Clear Selection",
getEnabled: function (context) {
return (context.viewer.scene.numSelectedObjects > 0);
},
doAction: function (context) {
context.viewer.scene.setObjectsSelected(context.viewer.scene.selectedObjectIds, false);
}
}
]
]
});
Sub-menus
Each menu item can optionally have a sub-menu, which will appear when we hover over the item.
In the example below, we'll create a much simpler ContextMenu
that has only one item, called "Effects", which
will open a cascading sub-menu whenever we hover over that item.
Note that our "Effects" item has no doAction
callback, because an item with a sub-menu performs no
action of its own.
const canvasContextMenu = new ContextMenu({
items: [ // Top level items
[
{
getTitle: (context) => {
return "Effects";
},
items: [ // Sub-menu
[
{
getTitle: (context) => {
return (!context.entity.visible) ? "Show" : "Hide";
},
doAction: function (context) {
context.entity.visible = !context.entity.visible;
}
},
{
getTitle: (context) => {
return (!context.entity.selected) ? "Select" : "Undo Select";
},
doAction: function (context) {
context.entity.selected = !context.entity.selected;
}
},
{
getTitle: (context) => {
return (!context.entity.highlighted) ? "Highlight" : "Undo Highlight";
},
doAction: function (context) {
context.entity.highlighted = !context.entity.highlighted;
}
}
]
]
}
]
]
});
Constructor Summary
Public Constructor | ||
public |
constructor(cfg: Object) Creates a |
Member Summary
Public Members | ||
public set |
Sets the |
|
public get |
Gets the |
|
public set |
Sets whether this |
|
public get |
Gets whether this |
|
public set |
Sets the |
|
public get |
Gets the |
|
public get |
Gets whether this |
Method Summary
Public Methods | ||
public |
destroy() Destroys this |
|
public |
Fires an event at this |
|
public |
hide() Hides this |
|
public |
Subscribes to an event fired at this |
|
public |
Shows this |
Public Constructors
public constructor(cfg: Object) source
Creates a ContextMenu
.
The ContextMenu
will be initially hidden.
Params:
Name | Type | Attribute | Description |
cfg | Object |
|
|
cfg.items | Object |
|
The context menu items. These can also be dynamically set on ContextMenu#items. See the class documentation for an example. |
cfg.context | Object |
|
The context, which is passed into the item callbacks. This can also be dynamically set on ContextMenu#context. This must be set before calling ContextMenu#show. |
cfg.enabled | Boolean |
|
Whether this |
cfg.hideOnMouseDown | Boolean |
|
Whether this |
cfg.hideOnAction | Boolean |
|
Whether this |
Public Members
public set context: Object source
Sets the ContextMenu
context.
The context can be any object that you need to be provides to the callbacks configured on ContextMenu#items.
This must be set before calling ContextMenu#show.
public set enabled: Boolean source
Sets whether this ContextMenu
is enabled.
Hides the menu when disabling.
public get enabled: Boolean source
Gets whether this ContextMenu
is enabled.
ContextMenu#show does nothing while this is false
.
Public Methods
public on(event: String, callback: Function) source
Subscribes to an event fired at this ContextMenu
.
public show(pageX: Number, pageY: Number) source
Shows this ContextMenu
at the given page coordinates.
Does nothing when ContextMenu#enabled is false
.
Logs error to console and does nothing if ContextMenu#context has not been set.
Fires a "shown" event when shown.