DevExtreme v24.2 is now available.

Explore our newest features/capabilities and share your thoughts with us.

Your search did not match any results.

React Tree View - 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.

Backend API
import React, { useCallback, useRef, useState } from 'react'; import TreeView, { TreeViewTypes } from 'devextreme-react/tree-view'; import ContextMenu, { ContextMenuTypes } from 'devextreme-react/context-menu'; import List from 'devextreme-react/list'; import service from './data.ts'; const products = service.getProducts(); const menuItems = service.getMenuItems(); const App = () => { const contextMenuRef = useRef(null); const treeViewRef = useRef(null); const [logItems, setLogItems] = useState([]); const [selectedTreeItem, setSelectedTreeItem] = useState(undefined); const treeViewItemContextMenu = useCallback(( e: TreeViewTypes.ItemContextMenuEvent & { itemData: { price?: any; }; }, ) => { setSelectedTreeItem(e.itemData); const isProduct = e.itemData.price !== undefined; contextMenuRef.current.instance().option('items[0].visible', !isProduct); contextMenuRef.current.instance().option('items[1].visible', !isProduct); contextMenuRef.current.instance().option('items[2].visible', isProduct); contextMenuRef.current.instance().option('items[3].visible', isProduct); contextMenuRef.current.instance().option('items[0].disabled', e.node.expanded); contextMenuRef.current.instance().option('items[1].disabled', !e.node.expanded); }, []); const contextMenuItemClick = useCallback(( e: ContextMenuTypes.ItemClickEvent & { itemData: { id?: any; }; }, ) => { let logEntry = ''; switch (e.itemData.id) { case 'expand': { logEntry = `The '${selectedTreeItem.text}' group was expanded`; treeViewRef.current.instance().expandItem(selectedTreeItem.id); break; } case 'collapse': { logEntry = `The '${selectedTreeItem.text}' group was collapsed`; treeViewRef.current.instance().collapseItem(selectedTreeItem.id); break; } case 'details': { logEntry = `Details about '${selectedTreeItem.text}' were displayed`; break; } case 'copy': { logEntry = `Information about '${selectedTreeItem.text}' was copied`; break; } default: break; } const updatedLogItems = [...logItems, logEntry]; setLogItems(updatedLogItems); }, [logItems, selectedTreeItem, setLogItems]); return ( <div className="form"> <TreeView id="treeview" ref={treeViewRef} items={products} width={300} height={450} onItemContextMenu={treeViewItemContextMenu} /> <div className="log-container"> <div> <i className="icon dx-icon-clock"></i>&nbsp;Operations log: </div> <List id="log" width={400} height={300} showScrollbar="always" items={logItems} /> </div> <ContextMenu ref={contextMenuRef} dataSource={menuItems} target="#treeview .dx-treeview-item" onItemClick={contextMenuItemClick} /> </div> ); }; export default App;
import React, { useCallback, useRef, useState } 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'; const products = service.getProducts(); const menuItems = service.getMenuItems(); const App = () => { const contextMenuRef = useRef(null); const treeViewRef = useRef(null); const [logItems, setLogItems] = useState([]); const [selectedTreeItem, setSelectedTreeItem] = useState(undefined); const treeViewItemContextMenu = useCallback((e) => { setSelectedTreeItem(e.itemData); const isProduct = e.itemData.price !== undefined; contextMenuRef.current.instance().option('items[0].visible', !isProduct); contextMenuRef.current.instance().option('items[1].visible', !isProduct); contextMenuRef.current.instance().option('items[2].visible', isProduct); contextMenuRef.current.instance().option('items[3].visible', isProduct); contextMenuRef.current.instance().option('items[0].disabled', e.node.expanded); contextMenuRef.current.instance().option('items[1].disabled', !e.node.expanded); }, []); const contextMenuItemClick = useCallback( (e) => { let logEntry = ''; switch (e.itemData.id) { case 'expand': { logEntry = `The '${selectedTreeItem.text}' group was expanded`; treeViewRef.current.instance().expandItem(selectedTreeItem.id); break; } case 'collapse': { logEntry = `The '${selectedTreeItem.text}' group was collapsed`; treeViewRef.current.instance().collapseItem(selectedTreeItem.id); break; } case 'details': { logEntry = `Details about '${selectedTreeItem.text}' were displayed`; break; } case 'copy': { logEntry = `Information about '${selectedTreeItem.text}' was copied`; break; } default: break; } const updatedLogItems = [...logItems, logEntry]; setLogItems(updatedLogItems); }, [logItems, selectedTreeItem, setLogItems], ); return ( <div className="form"> <TreeView id="treeview" ref={treeViewRef} items={products} width={300} height={450} onItemContextMenu={treeViewItemContextMenu} /> <div className="log-container"> <div> <i className="icon dx-icon-clock"></i>&nbsp;Operations log: </div> <List id="log" width={400} height={300} showScrollbar="always" items={logItems} /> </div> <ContextMenu ref={contextMenuRef} dataSource={menuItems} target="#treeview .dx-treeview-item" onItemClick={contextMenuItemClick} /> </div> ); }; export default App;
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.tsx'; ReactDOM.render( <App />, document.getElementById('app'), );
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: 'ts', typescriptOptions: { module: 'system', emitDecoratorMetadata: true, experimentalDecorators: true, jsx: 'react', }, meta: { 'react': { 'esModule': true, }, 'typescript': { 'exports': 'ts', }, 'devextreme/time_zone_utils.js': { 'esModule': true, }, 'devextreme/localization.js': { 'esModule': true, }, 'devextreme/viz/palette.js': { 'esModule': true, }, 'openai': { 'esModule': true, }, }, paths: { 'npm:': 'https://unpkg.com/', 'bundles:': '../../../../bundles/', 'externals:': '../../../../bundles/externals/', }, defaultExtension: 'js', map: { 'ts': 'npm:plugin-typescript@8.0.0/lib/plugin.js', 'typescript': 'npm:typescript@4.2.4/lib/typescript.js', 'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js', '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/prop-types.js', 'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js', 'luxon': 'npm:luxon@3.4.4/build/global/luxon.min.js', 'es6-object-assign': 'npm:es6-object-assign', 'devextreme': 'npm:devextreme@link:../../packages/devextreme/artifacts/npm/devextreme/cjs', 'devextreme-react': 'npm:devextreme-react@link:../../packages/devextreme-react/npm/cjs', 'devextreme-quill': 'npm:devextreme-quill@1.7.1/dist/dx-quill.min.js', 'devexpress-diagram': 'npm:devexpress-diagram@2.2.5/dist/dx-diagram.js', 'devexpress-gantt': 'npm:devexpress-gantt@4.1.54/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/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', 'devextreme-cldr-data': 'npm:devextreme-cldr-data@1.0.3', // 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.8/standalone.js', 'prettier/parser-html': 'npm:prettier@2.8.8/parser-html.js', }, packages: { 'devextreme': { defaultExtension: 'js', }, 'devextreme-react': { main: 'index.js', }, 'devextreme/events/utils': { main: 'index', }, 'devextreme/localization/messages': { format: 'json', defaultExtension: 'json', }, '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);
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.js'; ReactDOM.render(<App />, document.getElementById('app'));
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; }, };
<!DOCTYPE html> <html lang="en"> <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=5.0" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/24.2.3/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.tsx"); </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; }