ContextMenu Integration
To display a DevExtreme ContextMenu when users right-click TreeView nodes, specify the nodes as the menu's target elements. To do this, set the menu's target property to a CSS selector. Since all TreeView nodes use the dx-treeview-item
class, you can use this class' selector, as shown in this demo.
You can control each command's enabled state depending on the node where users invoked the context menu. Implement the TreeView's onItemContextMenu function; in it, set the disabled property to true for required commands.
Feel free to share demo-related thoughts here.
If you have technical questions, please create a support ticket in the DevExpress Support Center.
Thank you for the feedback!
If you have technical questions, please create a support ticket in the DevExpress Support Center.
Backend API
import React from 'react';
import TreeView from 'devextreme-react/tree-view';
import ContextMenu from 'devextreme-react/context-menu';
import List from 'devextreme-react/list';
import service from './data.js';
class App extends React.Component {
constructor(props) {
super(props);
this.contextMenuRef = React.createRef();
this.treeViewRef = React.createRef();
this.state = {
products: service.getProducts(),
menuItems: service.getMenuItems(),
logItems: [],
selectedTreeItem: undefined,
};
this.treeViewItemContextMenu = this.treeViewItemContextMenu.bind(this);
this.contextMenuItemClick = this.contextMenuItemClick.bind(this);
}
render() {
return (
<div className="form">
<TreeView id="treeview"
ref={this.treeViewRef}
items={this.state.products}
width={300}
height={450}
onItemContextMenu={this.treeViewItemContextMenu} />
<div className="log-container">
<div><i className="icon dx-icon-clock"></i> Operations log:</div>
<List
id="log"
width={400}
height={300}
showScrollbar="always"
items={this.state.logItems} />
</div>
<ContextMenu
ref={this.contextMenuRef}
dataSource={this.state.menuItems}
target="#treeview .dx-treeview-item"
onItemClick={this.contextMenuItemClick} />
</div>
);
}
get treeView() {
return this.treeViewRef.current.instance;
}
get contextMenu() {
return this.contextMenuRef.current.instance;
}
treeViewItemContextMenu(e) {
this.setState({
selectedTreeItem: e.itemData,
});
const isProduct = e.itemData.price !== undefined;
this.contextMenu.option('items[0].visible', !isProduct);
this.contextMenu.option('items[1].visible', !isProduct);
this.contextMenu.option('items[2].visible', isProduct);
this.contextMenu.option('items[3].visible', isProduct);
this.contextMenu.option('items[0].disabled', e.node.expanded);
this.contextMenu.option('items[1].disabled', !e.node.expanded);
}
contextMenuItemClick(e) {
let logEntry = '';
switch (e.itemData.id) {
case 'expand': {
logEntry = `The '${this.state.selectedTreeItem.text}' group was expanded`;
this.treeView.expandItem(this.state.selectedTreeItem.id);
break;
}
case 'collapse': {
logEntry = `The '${this.state.selectedTreeItem.text}' group was collapsed`;
this.treeView.collapseItem(this.state.selectedTreeItem.id);
break;
}
case 'details': {
logEntry = `Details about '${this.state.selectedTreeItem.text}' were displayed`;
break;
}
case 'copy': {
logEntry = `Information about '${this.state.selectedTreeItem.text}' was copied`;
break;
}
default:
break;
}
const logItems = this.state.logItems.concat([logEntry]);
this.setState({
logItems,
});
}
}
export default App;
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.js';
ReactDOM.render(
<App />,
document.getElementById('app'),
);
<!DOCTYPE html>
<html>
<head>
<title>DevExtreme Demo</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/23.1.5/css/dx.light.css" />
<link rel="stylesheet" type="text/css" href="styles.css" />
<script src="https://unpkg.com/core-js@2.6.12/client/shim.min.js"></script>
<script src="https://unpkg.com/systemjs@0.21.3/dist/system.js"></script>
<script type="text/javascript" src="config.js"></script>
<script type="text/javascript">
System.import("./index.js");
</script>
</head>
<body class="dx-viewport">
<div class="demo-container">
<div id="app"></div>
</div>
</body>
</html>
.form {
display: flex;
}
.form > div,
#treeview {
display: inline-block;
vertical-align: top;
}
.log-container {
padding: 20px;
margin-left: 20px;
font-size: 115%;
font-weight: bold;
background-color: rgba(191, 191, 191, 0.15);
height: 100%;
}
.log-container .dx-icon-clock {
position: relative;
top: 1px;
}
#log {
margin-top: 10px;
}
#log .dx-empty-message {
padding-left: 0;
}
.dx-list-item-content {
padding-left: 0;
}
const menuItems = [
{ id: 'expand', text: 'Expand category' },
{ id: 'collapse', text: 'Collapse category' },
{ id: 'details', text: 'Show product details' },
{ id: 'copy', text: 'Copy product info' },
];
const products = [{
id: '1',
text: 'Stores',
expanded: true,
items: [{
id: '1_1',
text: 'Super Mart of the West',
expanded: true,
items: [{
id: '1_1_1',
text: 'Video Players',
items: [{
id: '1_1_1_1',
text: 'HD Video Player',
price: 220,
image: '../../../../images/products/1.png',
}, {
id: '1_1_1_2',
text: 'SuperHD Video Player',
image: '../../../../images/products/2.png',
price: 270,
}],
}, {
id: '1_1_2',
text: 'Televisions',
expanded: true,
items: [{
id: '1_1_2_1',
text: 'SuperLCD 42',
image: '../../../../images/products/7.png',
price: 1200,
}, {
id: '1_1_2_2',
text: 'SuperLED 42',
image: '../../../../images/products/5.png',
price: 1450,
}, {
id: '1_1_2_3',
text: 'SuperLED 50',
image: '../../../../images/products/4.png',
price: 1600,
}, {
id: '1_1_2_4',
text: 'SuperLCD 55',
image: '../../../../images/products/6.png',
price: 1350,
}, {
id: '1_1_2_5',
text: 'SuperLCD 70',
image: '../../../../images/products/9.png',
price: 4000,
}],
}, {
id: '1_1_3',
text: 'Monitors',
expanded: true,
items: [{
id: '1_1_3_1',
text: '19"',
expanded: true,
items: [{
id: '1_1_3_1_1',
text: 'DesktopLCD 19',
image: '../../../../images/products/10.png',
price: 160,
}],
}, {
id: '1_1_3_2',
text: '21"',
items: [{
id: '1_1_3_2_1',
text: 'DesktopLCD 21',
image: '../../../../images/products/12.png',
price: 170,
}, {
id: '1_1_3_2_2',
text: 'DesktopLED 21',
image: '../../../../images/products/13.png',
price: 175,
}],
}],
}, {
id: '1_1_4',
text: 'Projectors',
items: [{
id: '1_1_4_1',
text: 'Projector Plus',
image: '../../../../images/products/14.png',
price: 550,
}, {
id: '1_1_4_2',
text: 'Projector PlusHD',
image: '../../../../images/products/15.png',
price: 750,
}],
}],
}, {
id: '1_2',
text: 'Braeburn',
items: [{
id: '1_2_1',
text: 'Video Players',
items: [{
id: '1_2_1_1',
text: 'HD Video Player',
image: '../../../../images/products/1.png',
price: 240,
}, {
id: '1_2_1_2',
text: 'SuperHD Video Player',
image: '../../../../images/products/2.png',
price: 300,
}],
}, {
id: '1_2_2',
text: 'Televisions',
items: [{
id: '1_2_2_1',
text: 'SuperPlasma 50',
image: '../../../../images/products/3.png',
price: 1800,
}, {
id: '1_2_2_2',
text: 'SuperPlasma 65',
image: '../../../../images/products/8.png',
price: 3500,
}],
}, {
id: '1_2_3',
text: 'Monitors',
items: [{
id: '1_2_3_1',
text: '19"',
items: [{
id: '1_2_3_1_1',
text: 'DesktopLCD 19',
image: '../../../../images/products/10.png',
price: 170,
}],
}, {
id: '1_2_3_2',
text: '21"',
items: [{
id: '1_2_3_2_1',
text: 'DesktopLCD 21',
image: '../../../../images/products/12.png',
price: 180,
}, {
id: '1_2_3_2_2',
text: 'DesktopLED 21',
image: '../../../../images/products/13.png',
price: 190,
}],
}],
}],
}, {
id: '1_3',
text: 'E-Mart',
items: [{
id: '1_3_1',
text: 'Video Players',
items: [{
id: '1_3_1_1',
text: 'HD Video Player',
image: '../../../../images/products/1.png',
price: 220,
}, {
id: '1_3_1_2',
text: 'SuperHD Video Player',
image: '../../../../images/products/2.png',
price: 275,
}],
}, {
id: '1_3_3',
text: 'Monitors',
items: [{
id: '1_3_3_1',
text: '19"',
items: [{
id: '1_3_3_1_1',
text: 'DesktopLCD 19',
image: '../../../../images/products/10.png',
price: 165,
}],
}, {
id: '1_3_3_2',
text: '21"',
items: [{
id: '1_3_3_2_1',
text: 'DesktopLCD 21',
image: '../../../../images/products/12.png',
price: 175,
}],
}],
}],
}, {
id: '1_4',
text: 'Walters',
items: [{
id: '1_4_1',
text: 'Video Players',
items: [{
id: '1_4_1_1',
text: 'HD Video Player',
image: '../../../../images/products/1.png',
price: 210,
}, {
id: '1_4_1_2',
text: 'SuperHD Video Player',
image: '../../../../images/products/2.png',
price: 250,
}],
}, {
id: '1_4_2',
text: 'Televisions',
items: [{
id: '1_4_2_1',
text: 'SuperLCD 42',
image: '../../../../images/products/7.png',
price: 1100,
}, {
id: '1_4_2_2',
text: 'SuperLED 42',
image: '../../../../images/products/5.png',
price: 1400,
}, {
id: '1_4_2_3',
text: 'SuperLED 50',
image: '../../../../images/products/4.png',
price: 1500,
}, {
id: '1_4_2_4',
text: 'SuperLCD 55',
image: '../../../../images/products/6.png',
price: 1300,
}, {
id: '1_4_2_5',
text: 'SuperLCD 70',
image: '../../../../images/products/9.png',
price: 4000,
}, {
id: '1_4_2_6',
text: 'SuperPlasma 50',
image: '../../../../images/products/3.png',
price: 1700,
}],
}, {
id: '1_4_3',
text: 'Monitors',
items: [{
id: '1_4_3_1',
text: '19"',
items: [{
id: '1_4_3_1_1',
text: 'DesktopLCD 19',
image: '../../../../images/products/10.png',
price: 160,
}],
}, {
id: '1_4_3_2',
text: '21"',
items: [{
id: '1_4_3_2_1',
text: 'DesktopLCD 21',
image: '../../../../images/products/12.png',
price: 170,
}, {
id: '1_4_3_2_2',
text: 'DesktopLED 21',
image: '../../../../images/products/13.png',
price: 180,
}],
}],
}, {
id: '1_4_4',
text: 'Projectors',
items: [{
id: '1_4_4_1',
text: 'Projector Plus',
image: '../../../../images/products/14.png',
price: 550,
}, {
id: '1_4_4_2',
text: 'Projector PlusHD',
image: '../../../../images/products/15.png',
price: 750,
}],
}],
}],
}];
export default {
getProducts() {
return products;
},
getMenuItems() {
return menuItems;
},
};
window.exports = window.exports || {};
window.config = {
transpiler: 'plugin-babel',
meta: {
'devextreme/localization.js': {
'esModule': true,
},
},
paths: {
'npm:': 'https://unpkg.com/',
},
defaultExtension: 'js',
map: {
'react': 'npm:react@17.0.2/umd/react.development.js',
'react-dom': 'npm:react-dom@17.0.2/umd/react-dom.development.js',
'prop-types': 'npm:prop-types@15.8.1/prop-types.js',
'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js',
'luxon': 'npm:luxon@1.28.1/build/global/luxon.min.js',
'es6-object-assign': 'npm:es6-object-assign@1.1.0',
'devextreme': 'npm:devextreme@23.1.6/cjs',
'devextreme-react': 'npm:devextreme-react@23.1.6',
'jszip': 'npm:jszip@3.7.1/dist/jszip.min.js',
'devextreme-quill': 'npm:devextreme-quill@1.6.2/dist/dx-quill.min.js',
'devexpress-diagram': 'npm:devexpress-diagram@2.2.2/dist/dx-diagram.js',
'devexpress-gantt': 'npm:devexpress-gantt@4.1.49/dist/dx-gantt.js',
'@devextreme/runtime': 'npm:@devextreme/runtime@3.0.12',
'inferno': 'npm:inferno@7.4.11/dist/inferno.min.js',
'inferno-compat': 'npm:inferno-compat/dist/inferno-compat.min.js',
'inferno-create-element': 'npm:inferno-create-element@7.4.11/dist/inferno-create-element.min.js',
'inferno-dom': 'npm:inferno-dom/dist/inferno-dom.min.js',
'inferno-hydrate': 'npm:inferno-hydrate@7.4.11/dist/inferno-hydrate.min.js',
'inferno-clone-vnode': 'npm:inferno-clone-vnode/dist/inferno-clone-vnode.min.js',
'inferno-create-class': 'npm:inferno-create-class/dist/inferno-create-class.min.js',
'inferno-extras': 'npm:inferno-extras/dist/inferno-extras.min.js',
// SystemJS plugins
'plugin-babel': 'npm:systemjs-plugin-babel@0.0.25/plugin-babel.js',
'systemjs-babel-build': 'npm:systemjs-plugin-babel@0.0.25/systemjs-babel-browser.js',
// Prettier
'prettier/standalone': 'npm:prettier@2.8.4/standalone.js',
'prettier/parser-html': 'npm:prettier@2.8.4/parser-html.js',
},
packages: {
'devextreme': {
defaultExtension: 'js',
},
'devextreme-react': {
main: 'index.js',
},
'devextreme/events/utils': {
main: 'index',
},
'devextreme/events': {
main: 'index',
},
'es6-object-assign': {
main: './index.js',
defaultExtension: 'js',
},
},
packageConfigPaths: [
'npm:@devextreme/*/package.json',
'npm:@devextreme/runtime@3.0.12/inferno/package.json',
],
babelOptions: {
sourceMaps: false,
stage0: true,
react: true,
},
};
System.config(window.config);