DevExtreme v24.1 is now available.

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

Your search did not match any results.

React Diagram - Templates with Editing

In this demo, the customShapeTemplate property defines a common shape template and adds the Edit and Delete links to a shape. These links allow users to modify and remove employee data from the data source. The Diagram component reloads modified diagram data whenever the data source changes.

The onRequestLayoutUpdate function specifies whether the component must reapply its auto layout once the diagram is reloaded.

Backend API
import React, { useCallback, useRef, useState } from 'react'; import { Diagram, AutoLayout, ContextToolbox, CustomShape, DiagramRef, Group, Nodes, PropertiesPanel, Tab, Toolbox, DiagramTypes, } from 'devextreme-react/diagram'; import { Popup } from 'devextreme-react/popup'; import TextBox from 'devextreme-react/text-box'; import Button from 'devextreme-react/button'; import ArrayStore from 'devextreme/data/array_store'; import CustomShapeTemplate from './CustomShapeTemplate.tsx'; import CustomShapeToolboxTemplate from './CustomShapeToolboxTemplate.tsx'; import service, { Employee } from './data.ts'; const pageCommands: DiagramTypes.Command[] = ['pageSize', 'pageOrientation', 'pageColor']; const nameLabel = { 'aria-label': 'Name' }; const emailLabel = { 'aria-label': 'Email' }; const titleLabel = { 'aria-label': 'Title' }; const stateLabel = { 'aria-label': 'State' }; const cityLabel = { 'aria-label': 'City' }; const phoneLabel = { 'aria-label': 'Phone' }; const skypeLabel = { 'aria-label': 'Skype' }; let generatedID = 100; const employees = service.getEmployees(); const dataSource = new ArrayStore({ key: 'ID', data: employees, onInserting(values, key) { this.update(key, { ID: values.ID || (generatedID += 1), Full_Name: values.Full_Name || "Employee's Name", Title: values.Title || "Employee's Title", }); }, } as any); function onRequestLayoutUpdate(e: DiagramTypes.RequestLayoutUpdateEvent) { for (let i = 0; i < e.changes.length; i += 1) { if (e.changes[i].type === 'remove') { e.allowed = true; } else if (e.changes[i].data.Head_ID !== undefined && e.changes[i].data.Head_ID !== null) { e.allowed = true; } } } function deleteEmployee(employee: Employee) { dataSource.push([{ type: 'remove', key: employee.ID }]); } function itemTypeExpr() { return 'employee'; } function itemCustomDataExpr(obj: Employee, value: Employee) { if (value === undefined) { return { Full_Name: obj.Full_Name, Prefix: obj.Prefix, Title: obj.Title, City: obj.City, State: obj.State, Email: obj.Email, Skype: obj.Skype, Mobile_Phone: obj.Mobile_Phone, }; } obj.Full_Name = value.Full_Name; obj.Prefix = value.Prefix; obj.Title = value.Title; obj.City = value.City; obj.State = value.State; obj.Email = value.Email; obj.Skype = value.Skype; obj.Mobile_Phone = value.Mobile_Phone; return null; } export default function App() { const [currentEmployee, setCurrentEmployee] = useState<Partial<Employee>>({}); const [popupVisible, setPopupVisible] = useState(false); const diagramRef = useRef<DiagramRef>(null); const editEmployee = useCallback((employee) => { setCurrentEmployee({ ...employee }); setPopupVisible(true); }, []); const updateEmployee = useCallback(() => { dataSource.push([{ type: 'update', key: currentEmployee.ID, data: { Full_Name: currentEmployee.Full_Name, Title: currentEmployee.Title, City: currentEmployee.City, State: currentEmployee.State, Email: currentEmployee.Email, Skype: currentEmployee.Skype, Mobile_Phone: currentEmployee.Mobile_Phone, }, }]); setCurrentEmployee({}); setPopupVisible(false); }, [currentEmployee, setCurrentEmployee, setPopupVisible]); const customShapeTemplate = useCallback((item) => (CustomShapeTemplate(item.dataItem, () => { editEmployee(item.dataItem); }, () => { deleteEmployee(item.dataItem); }) ), [editEmployee]); const customShapeToolboxTemplate = useCallback(() => CustomShapeToolboxTemplate(), []); const cancelEditEmployee = useCallback(() => { setCurrentEmployee({}); setPopupVisible(false); }, [setCurrentEmployee, setPopupVisible]); const handleChange = useCallback((field, value) => { setCurrentEmployee((prevState) => ({ ...prevState, [field]: value, })); }, [setCurrentEmployee]); const handleNameChange = useCallback((e: { value: any; }) => { handleChange('Full_Name', e.value); }, [handleChange]); const handleTitleChange = useCallback((e: { value: any; }) => { handleChange('Title', e.value); }, [handleChange]); const handleCityChange = useCallback((e: { value: any; }) => { handleChange('City', e.value); }, [handleChange]); const handleStateChange = useCallback((e: { value: any; }) => { handleChange('State', e.value); }, [handleChange]); const handleEmailChange = useCallback((e: { value: any; }) => { handleChange('Email', e.value); }, [handleChange]); const handleSkypeChange = useCallback((e: { value: any; }) => { handleChange('Skype', e.value); }, [handleChange]); const handlePhoneChange = useCallback((e: { value: any; }) => { handleChange('Mobile_Phone', e.value); }, [handleChange]); const popupContentRender = useCallback(() => ( <PopupContentFunc currentEmployee={currentEmployee} handleNameChange={handleNameChange} handleTitleChange={handleTitleChange} handleCityChange={handleCityChange} handleStateChange={handleStateChange} handleEmailChange={handleEmailChange} handleSkypeChange={handleSkypeChange} handlePhoneChange={handlePhoneChange} updateEmployeeClick={updateEmployee} cancelEditEmployeeClick={cancelEditEmployee} /> ), [ currentEmployee, handleNameChange, handleTitleChange, handleCityChange, handleStateChange, handleEmailChange, handleSkypeChange, handlePhoneChange, updateEmployee, cancelEditEmployee, ]); return ( <div id="container"> <Diagram id="diagram" ref={diagramRef} customShapeRender={customShapeTemplate} customShapeToolboxRender={customShapeToolboxTemplate} onRequestLayoutUpdate={onRequestLayoutUpdate} > <CustomShape type="employee" baseType="rectangle" category="employee" title="New Employee" defaultWidth={1.5} defaultHeight={1} toolboxWidthToHeightRatio={2} minWidth={1.5} minHeight={1} maxWidth={3} maxHeight={2} allowEditText={false} /> <Nodes dataSource={dataSource} keyExpr="ID" typeExpr={itemTypeExpr} customDataExpr={itemCustomDataExpr} parentKeyExpr="Head_ID" > <AutoLayout type="tree" /> </Nodes> <ContextToolbox shapeIconsPerRow={1} width={100} /> <Toolbox showSearch={false} shapeIconsPerRow={1}> <Group category={'employee' as any} title="Employee" expanded={true} /> </Toolbox> <PropertiesPanel> <Tab> <Group title="Page Properties" commands={pageCommands} /> </Tab> </PropertiesPanel> </Diagram> <Popup visible={popupVisible} onHiding={cancelEditEmployee} dragEnabled={false} showTitle={true} title="Edit Employee" width={400} height={480} contentRender={popupContentRender} /> </div> ); } function PopupContentFunc(props) { return ( <React.Fragment> <div className="dx-fieldset"> <div className="dx-field"> <div className="dx-field-label">Name</div> <div className="dx-field-value"> <TextBox inputAttr={nameLabel} value={props.currentEmployee.Full_Name} onValueChanged={props.handleNameChange} valueChangeEvent="input"> </TextBox> </div> </div> <div className="dx-field"> <div className="dx-field-label">Title</div> <div className="dx-field-value"> <TextBox inputAttr={titleLabel} value={props.currentEmployee.Title} onValueChanged={props.handleTitleChange} valueChangeEvent="input"> </TextBox> </div> </div> <div className="dx-field"> <div className="dx-field-label">City</div> <div className="dx-field-value"> <TextBox inputAttr={cityLabel} value={props.currentEmployee.City} onValueChanged={props.handleCityChange} valueChangeEvent="input"> </TextBox> </div> </div> <div className="dx-field"> <div className="dx-field-label">State</div> <div className="dx-field-value"> <TextBox inputAttr={stateLabel} value={props.currentEmployee.State} onValueChanged={props.handleStateChange} valueChangeEvent="input"> </TextBox> </div> </div> <div className="dx-field"> <div className="dx-field-label">Email</div> <div className="dx-field-value"> <TextBox inputAttr={emailLabel} value={props.currentEmployee.Email} onValueChanged={props.handleEmailChange} valueChangeEvent="input"> </TextBox> </div> </div> <div className="dx-field"> <div className="dx-field-label">Skype</div> <div className="dx-field-value"> <TextBox inputAttr={skypeLabel} value={props.currentEmployee.Skype} onValueChanged={props.handleSkypeChange} valueChangeEvent="input"> </TextBox> </div> </div> <div className="dx-field"> <div className="dx-field-label">Phone</div> <div className="dx-field-value"> <TextBox inputAttr={phoneLabel} value={props.currentEmployee.Mobile_Phone} onValueChanged={props.handlePhoneChange} valueChangeEvent="input"> </TextBox> </div> </div> </div> <div className="dx-fieldset buttons"> <Button text="Update" type="default" onClick={props.updateEmployeeClick}></Button> <Button text="Cancel" onClick={props.cancelEditEmployeeClick}></Button> </div> </React.Fragment> ); }
import React, { useCallback, useRef, useState } from 'react'; import { Diagram, AutoLayout, ContextToolbox, CustomShape, Group, Nodes, PropertiesPanel, Tab, Toolbox, } from 'devextreme-react/diagram'; import { Popup } from 'devextreme-react/popup'; import TextBox from 'devextreme-react/text-box'; import Button from 'devextreme-react/button'; import ArrayStore from 'devextreme/data/array_store'; import CustomShapeTemplate from './CustomShapeTemplate.js'; import CustomShapeToolboxTemplate from './CustomShapeToolboxTemplate.js'; import service from './data.js'; const pageCommands = ['pageSize', 'pageOrientation', 'pageColor']; const nameLabel = { 'aria-label': 'Name' }; const emailLabel = { 'aria-label': 'Email' }; const titleLabel = { 'aria-label': 'Title' }; const stateLabel = { 'aria-label': 'State' }; const cityLabel = { 'aria-label': 'City' }; const phoneLabel = { 'aria-label': 'Phone' }; const skypeLabel = { 'aria-label': 'Skype' }; let generatedID = 100; const employees = service.getEmployees(); const dataSource = new ArrayStore({ key: 'ID', data: employees, onInserting(values, key) { this.update(key, { ID: values.ID || (generatedID += 1), Full_Name: values.Full_Name || "Employee's Name", Title: values.Title || "Employee's Title", }); }, }); function onRequestLayoutUpdate(e) { for (let i = 0; i < e.changes.length; i += 1) { if (e.changes[i].type === 'remove') { e.allowed = true; } else if (e.changes[i].data.Head_ID !== undefined && e.changes[i].data.Head_ID !== null) { e.allowed = true; } } } function deleteEmployee(employee) { dataSource.push([{ type: 'remove', key: employee.ID }]); } function itemTypeExpr() { return 'employee'; } function itemCustomDataExpr(obj, value) { if (value === undefined) { return { Full_Name: obj.Full_Name, Prefix: obj.Prefix, Title: obj.Title, City: obj.City, State: obj.State, Email: obj.Email, Skype: obj.Skype, Mobile_Phone: obj.Mobile_Phone, }; } obj.Full_Name = value.Full_Name; obj.Prefix = value.Prefix; obj.Title = value.Title; obj.City = value.City; obj.State = value.State; obj.Email = value.Email; obj.Skype = value.Skype; obj.Mobile_Phone = value.Mobile_Phone; return null; } export default function App() { const [currentEmployee, setCurrentEmployee] = useState({}); const [popupVisible, setPopupVisible] = useState(false); const diagramRef = useRef(null); const editEmployee = useCallback((employee) => { setCurrentEmployee({ ...employee }); setPopupVisible(true); }, []); const updateEmployee = useCallback(() => { dataSource.push([ { type: 'update', key: currentEmployee.ID, data: { Full_Name: currentEmployee.Full_Name, Title: currentEmployee.Title, City: currentEmployee.City, State: currentEmployee.State, Email: currentEmployee.Email, Skype: currentEmployee.Skype, Mobile_Phone: currentEmployee.Mobile_Phone, }, }, ]); setCurrentEmployee({}); setPopupVisible(false); }, [currentEmployee, setCurrentEmployee, setPopupVisible]); const customShapeTemplate = useCallback( (item) => CustomShapeTemplate( item.dataItem, () => { editEmployee(item.dataItem); }, () => { deleteEmployee(item.dataItem); }, ), [editEmployee], ); const customShapeToolboxTemplate = useCallback(() => CustomShapeToolboxTemplate(), []); const cancelEditEmployee = useCallback(() => { setCurrentEmployee({}); setPopupVisible(false); }, [setCurrentEmployee, setPopupVisible]); const handleChange = useCallback( (field, value) => { setCurrentEmployee((prevState) => ({ ...prevState, [field]: value, })); }, [setCurrentEmployee], ); const handleNameChange = useCallback( (e) => { handleChange('Full_Name', e.value); }, [handleChange], ); const handleTitleChange = useCallback( (e) => { handleChange('Title', e.value); }, [handleChange], ); const handleCityChange = useCallback( (e) => { handleChange('City', e.value); }, [handleChange], ); const handleStateChange = useCallback( (e) => { handleChange('State', e.value); }, [handleChange], ); const handleEmailChange = useCallback( (e) => { handleChange('Email', e.value); }, [handleChange], ); const handleSkypeChange = useCallback( (e) => { handleChange('Skype', e.value); }, [handleChange], ); const handlePhoneChange = useCallback( (e) => { handleChange('Mobile_Phone', e.value); }, [handleChange], ); const popupContentRender = useCallback( () => ( <PopupContentFunc currentEmployee={currentEmployee} handleNameChange={handleNameChange} handleTitleChange={handleTitleChange} handleCityChange={handleCityChange} handleStateChange={handleStateChange} handleEmailChange={handleEmailChange} handleSkypeChange={handleSkypeChange} handlePhoneChange={handlePhoneChange} updateEmployeeClick={updateEmployee} cancelEditEmployeeClick={cancelEditEmployee} /> ), [ currentEmployee, handleNameChange, handleTitleChange, handleCityChange, handleStateChange, handleEmailChange, handleSkypeChange, handlePhoneChange, updateEmployee, cancelEditEmployee, ], ); return ( <div id="container"> <Diagram id="diagram" ref={diagramRef} customShapeRender={customShapeTemplate} customShapeToolboxRender={customShapeToolboxTemplate} onRequestLayoutUpdate={onRequestLayoutUpdate} > <CustomShape type="employee" baseType="rectangle" category="employee" title="New Employee" defaultWidth={1.5} defaultHeight={1} toolboxWidthToHeightRatio={2} minWidth={1.5} minHeight={1} maxWidth={3} maxHeight={2} allowEditText={false} /> <Nodes dataSource={dataSource} keyExpr="ID" typeExpr={itemTypeExpr} customDataExpr={itemCustomDataExpr} parentKeyExpr="Head_ID" > <AutoLayout type="tree" /> </Nodes> <ContextToolbox shapeIconsPerRow={1} width={100} /> <Toolbox showSearch={false} shapeIconsPerRow={1} > <Group category="employee" title="Employee" expanded={true} /> </Toolbox> <PropertiesPanel> <Tab> <Group title="Page Properties" commands={pageCommands} /> </Tab> </PropertiesPanel> </Diagram> <Popup visible={popupVisible} onHiding={cancelEditEmployee} dragEnabled={false} showTitle={true} title="Edit Employee" width={400} height={480} contentRender={popupContentRender} /> </div> ); } function PopupContentFunc(props) { return ( <React.Fragment> <div className="dx-fieldset"> <div className="dx-field"> <div className="dx-field-label">Name</div> <div className="dx-field-value"> <TextBox inputAttr={nameLabel} value={props.currentEmployee.Full_Name} onValueChanged={props.handleNameChange} valueChangeEvent="input" ></TextBox> </div> </div> <div className="dx-field"> <div className="dx-field-label">Title</div> <div className="dx-field-value"> <TextBox inputAttr={titleLabel} value={props.currentEmployee.Title} onValueChanged={props.handleTitleChange} valueChangeEvent="input" ></TextBox> </div> </div> <div className="dx-field"> <div className="dx-field-label">City</div> <div className="dx-field-value"> <TextBox inputAttr={cityLabel} value={props.currentEmployee.City} onValueChanged={props.handleCityChange} valueChangeEvent="input" ></TextBox> </div> </div> <div className="dx-field"> <div className="dx-field-label">State</div> <div className="dx-field-value"> <TextBox inputAttr={stateLabel} value={props.currentEmployee.State} onValueChanged={props.handleStateChange} valueChangeEvent="input" ></TextBox> </div> </div> <div className="dx-field"> <div className="dx-field-label">Email</div> <div className="dx-field-value"> <TextBox inputAttr={emailLabel} value={props.currentEmployee.Email} onValueChanged={props.handleEmailChange} valueChangeEvent="input" ></TextBox> </div> </div> <div className="dx-field"> <div className="dx-field-label">Skype</div> <div className="dx-field-value"> <TextBox inputAttr={skypeLabel} value={props.currentEmployee.Skype} onValueChanged={props.handleSkypeChange} valueChangeEvent="input" ></TextBox> </div> </div> <div className="dx-field"> <div className="dx-field-label">Phone</div> <div className="dx-field-value"> <TextBox inputAttr={phoneLabel} value={props.currentEmployee.Mobile_Phone} onValueChanged={props.handlePhoneChange} valueChangeEvent="input" ></TextBox> </div> </div> </div> <div className="dx-fieldset buttons"> <Button text="Update" type="default" onClick={props.updateEmployeeClick} ></Button> <Button text="Cancel" onClick={props.cancelEditEmployeeClick} ></Button> </div> </React.Fragment> ); }
import React from 'react'; import { Employee } from './data'; export default function CustomShapeTemplate(employee: Employee, editEmployee, deleteEmployee) { const employeeName = employee ? employee.Full_Name : 'Employee\'s Name'; const employeeTitle = employee ? employee.Title : 'Employee\'s Title'; return ( <svg className="template"> <text className="template-name" x="50%" y="20%">{employeeName}</text> <text className="template-title" x="50%" y="45%">{employeeTitle}</text> <text className="template-button" x="40%" y="85%" onClick={editEmployee}>Edit</text> <text className="template-button" x="62%" y="85%" onClick={deleteEmployee}>Delete</text> </svg> ); }
import React from 'react'; export default function CustomShapeToolboxTemplate() { return ( <svg className="template"> <text x="50%" y="40%">New</text> <text x="50%" y="70%">Employee</text> </svg> ); }
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.tsx'; ReactDOM.render( <App />, document.getElementById('app'), );
export interface Employee { ID: number, Head_ID: number, Full_Name: string, Prefix: string, Title: string, City: string, State: string, Email: string, Skype: string, Mobile_Phone: string, Birth_Date: string, Hire_Date: string, } const employees: Employee[] = [{ ID: 1, Head_ID: 0, Full_Name: 'John Heart', Prefix: 'Mr.', Title: 'CEO', City: 'Los Angeles', State: 'California', Email: 'jheart@dx-email.com', Skype: 'jheart_DX_skype', Mobile_Phone: '(213) 555-9392', Birth_Date: '1964-03-16', Hire_Date: '1995-01-15', }, { ID: 2, Head_ID: 1, Full_Name: 'Samantha Bright', Prefix: 'Dr.', Title: 'COO', City: 'Los Angeles', State: 'California', Email: 'samanthab@dx-email.com', Skype: 'samanthab_DX_skype', Mobile_Phone: '(213) 555-2858', Birth_Date: '1966-05-02', Hire_Date: '2004-05-24', }, { ID: 3, Head_ID: 1, Full_Name: 'Arthur Miller', Prefix: 'Mr.', Title: 'CTO', City: 'Denver', State: 'Colorado', Email: 'arthurm@dx-email.com', Skype: 'arthurm_DX_skype', Mobile_Phone: '(310) 555-8583', Birth_Date: '1972-07-11', Hire_Date: '2007-12-18', }, { ID: 4, Head_ID: 1, Full_Name: 'Robert Reagan', Prefix: 'Mr.', Title: 'CMO', City: 'Bentonville', State: 'Arkansas', Email: 'robertr@dx-email.com', Skype: 'robertr_DX_skype', Mobile_Phone: '(818) 555-2387', Birth_Date: '1974-09-07', Hire_Date: '2002-11-08', }, { ID: 5, Head_ID: 1, Full_Name: 'Greta Sims', Prefix: 'Ms.', Title: 'HR Manager', City: 'Atlanta', State: 'Georgia', Email: 'gretas@dx-email.com', Skype: 'gretas_DX_skype', Mobile_Phone: '(818) 555-6546', Birth_Date: '1977-11-22', Hire_Date: '1998-04-23', }, { ID: 6, Head_ID: 3, Full_Name: 'Brett Wade', Prefix: 'Mr.', Title: 'IT Manager', City: 'Reno', State: 'Nevada', Email: 'brettw@dx-email.com', Skype: 'brettw_DX_skype', Mobile_Phone: '(626) 555-0358', Birth_Date: '1968-12-01', Hire_Date: '2009-03-06', }, { ID: 7, Head_ID: 5, Full_Name: 'Sandra Johnson', Prefix: 'Mrs.', Title: 'Controller', City: 'Beaver', State: 'Utah', Email: 'sandraj@dx-email.com', Skype: 'sandraj_DX_skype', Mobile_Phone: '(562) 555-2082', Birth_Date: '1974-11-15', Hire_Date: '2005-05-11', }, { ID: 8, Head_ID: 4, Full_Name: 'Ed Holmes', Prefix: 'Dr.', Title: 'Sales Manager', City: 'Malibu', State: 'California', Email: 'edwardh@dx-email.com', Skype: 'edwardh_DX_skype', Mobile_Phone: '(310) 555-1288', Birth_Date: '1973-07-14', Hire_Date: '2005-06-19', }, { ID: 9, Head_ID: 3, Full_Name: 'Barb Banks', Prefix: 'Mrs.', Title: 'Support Manager', City: 'Phoenix', State: 'Arizona', Email: 'barbarab@dx-email.com', Skype: 'barbarab_DX_skype', Mobile_Phone: '(310) 555-3355', Birth_Date: '1979-04-14', Hire_Date: '2002-08-07', }, { ID: 10, Head_ID: 2, Full_Name: 'Kevin Carter', Prefix: 'Mr.', Title: 'Shipping Manager', City: 'San Diego', State: 'California', Email: 'kevinc@dx-email.com', Skype: 'kevinc_DX_skype', Mobile_Phone: '(213) 555-2840', Birth_Date: '1978-01-09', Hire_Date: '2009-08-11', }, { ID: 11, Head_ID: 5, Full_Name: 'Cindy Stanwick', Prefix: 'Ms.', Title: 'HR Assistant', City: 'Little Rock', State: 'Arkansas', Email: 'cindys@dx-email.com', Skype: 'cindys_DX_skype', Mobile_Phone: '(818) 555-6655', Birth_Date: '1985-06-05', Hire_Date: '2008-03-24', }, { ID: 12, Head_ID: 8, Full_Name: 'Sammy Hill', Prefix: 'Mr.', Title: 'Sales Assistant', City: 'Pasadena', State: 'California', Email: 'sammyh@dx-email.com', Skype: 'sammyh_DX_skype', Mobile_Phone: '(626) 555-7292', Birth_Date: '1984-02-17', Hire_Date: '2012-02-01', }, { ID: 13, Head_ID: 10, Full_Name: 'Davey Jones', Prefix: 'Mr.', Title: 'Shipping Assistant', City: 'Pasadena', State: 'California', Email: 'davidj@dx-email.com', Skype: 'davidj_DX_skype', Mobile_Phone: '(626) 555-0281', Birth_Date: '1983-03-06', Hire_Date: '2011-04-24', }, { ID: 14, Head_ID: 10, Full_Name: 'Victor Norris', Prefix: 'Mr.', Title: 'Shipping Assistant', City: 'Little Rock', State: 'Arkansas', Email: 'victorn@dx-email.com', Skype: 'victorn_DX_skype', Mobile_Phone: '(213) 555-9278', Birth_Date: '1986-07-23', Hire_Date: '2012-07-23', }, { ID: 15, Head_ID: 10, Full_Name: 'Mary Stern', Prefix: 'Ms.', Title: 'Shipping Assistant', City: 'Beaver', State: 'Utah', Email: 'marys@dx-email.com', Skype: 'marys_DX_skype', Mobile_Phone: '(818) 555-7857', Birth_Date: '1982-04-08', Hire_Date: '2012-08-12', }, { ID: 16, Head_ID: 10, Full_Name: 'Robin Cosworth', Prefix: 'Mrs.', Title: 'Shipping Assistant', City: 'Los Angeles', State: 'California', Email: 'robinc@dx-email.com', Skype: 'robinc_DX_skype', Mobile_Phone: '(818) 555-0942', Birth_Date: '1981-06-12', Hire_Date: '2012-09-01', }, { ID: 17, Head_ID: 9, Full_Name: 'Kelly Rodriguez', Prefix: 'Ms.', Title: 'Support Assistant', City: 'Boise', State: 'Idaho', Email: 'kellyr@dx-email.com', Skype: 'kellyr_DX_skype', Mobile_Phone: '(818) 555-9248', Birth_Date: '1988-05-11', Hire_Date: '2012-10-13', }, { ID: 18, Head_ID: 9, Full_Name: 'James Anderson', Prefix: 'Mr.', Title: 'Support Assistant', City: 'Atlanta', State: 'Georgia', Email: 'jamesa@dx-email.com', Skype: 'jamesa_DX_skype', Mobile_Phone: '(323) 555-4702', Birth_Date: '1987-01-29', Hire_Date: '2012-10-18', }, { ID: 19, Head_ID: 9, Full_Name: 'Antony Remmen', Prefix: 'Mr.', Title: 'Support Assistant', City: 'Boise', State: 'Idaho', Email: 'anthonyr@dx-email.com', Skype: 'anthonyr_DX_skype', Mobile_Phone: '(310) 555-6625', Birth_Date: '1986-02-19', Hire_Date: '2013-01-19', }, { ID: 20, Head_ID: 8, Full_Name: 'Olivia Peyton', Prefix: 'Mrs.', Title: 'Sales Assistant', City: 'Atlanta', State: 'Georgia', Email: 'oliviap@dx-email.com', Skype: 'oliviap_DX_skype', Mobile_Phone: '(310) 555-2728', Birth_Date: '1981-06-03', Hire_Date: '2012-05-14', }, { ID: 21, Head_ID: 6, Full_Name: 'Taylor Riley', Prefix: 'Mr.', Title: 'Network Admin', City: 'San Jose', State: 'California', Email: 'taylorr@dx-email.com', Skype: 'taylorr_DX_skype', Mobile_Phone: '(310) 555-7276', Birth_Date: '1982-08-14', Hire_Date: '2012-04-14', }, { ID: 22, Head_ID: 6, Full_Name: 'Amelia Harper', Prefix: 'Mrs.', Title: 'Network Admin', City: 'Los Angeles', State: 'California', Email: 'ameliah@dx-email.com', Skype: 'ameliah_DX_skype', Mobile_Phone: '(213) 555-4276', Birth_Date: '1983-11-19', Hire_Date: '2011-02-10', }, { ID: 23, Head_ID: 6, Full_Name: 'Wally Hobbs', Prefix: 'Mr.', Title: 'Programmer', City: 'Chatsworth', State: 'California', Email: 'wallyh@dx-email.com', Skype: 'wallyh_DX_skype', Mobile_Phone: '(818) 555-8872', Birth_Date: '1984-12-24', Hire_Date: '2011-02-17', }, { ID: 24, Head_ID: 6, Full_Name: 'Brad Jameson', Prefix: 'Mr.', Title: 'Programmer', City: 'San Fernando', State: 'California', Email: 'bradleyj@dx-email.com', Skype: 'bradleyj_DX_skype', Mobile_Phone: '(818) 555-4646', Birth_Date: '1988-10-12', Hire_Date: '2011-03-02', }, { ID: 25, Head_ID: 6, Full_Name: 'Karen Goodson', Prefix: 'Miss', Title: 'Programmer', City: 'South Pasadena', State: 'California', Email: 'kareng@dx-email.com', Skype: 'kareng_DX_skype', Mobile_Phone: '(626) 555-0908', Birth_Date: '1987-04-26', Hire_Date: '2011-03-14', }, { ID: 26, Head_ID: 5, Full_Name: 'Marcus Orbison', Prefix: 'Mr.', Title: 'Travel Coordinator', City: 'Los Angeles', State: 'California', Email: 'marcuso@dx-email.com', Skype: 'marcuso_DX_skype', Mobile_Phone: '(213) 555-7098', Birth_Date: '1982-03-02', Hire_Date: '2005-05-19', }]; export default { getEmployees() { return employees; }, };
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, }, }, paths: { 'npm:': 'https://unpkg.com/', }, defaultExtension: 'js', map: { 'ts': 'npm:plugin-typescript@4.2.4/lib/plugin.js', 'typescript': 'npm:typescript@4.2.4/lib/typescript.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@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@24.1.7/cjs', 'devextreme-react': 'npm:devextreme-react@24.1.7/cjs', 'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js', 'devextreme-quill': 'npm:devextreme-quill@1.7.1/dist/dx-quill.min.js', 'devexpress-diagram': 'npm:devexpress-diagram@2.2.13/dist/dx-diagram.js', 'devexpress-gantt': 'npm:devexpress-gantt@4.1.56/dist/dx-gantt.js', '@devextreme/runtime': 'npm:@devextreme/runtime@3.0.13', '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', '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.13/inferno/package.json', ], babelOptions: { sourceMaps: false, stage0: true, react: true, }, }; System.config(window.config);
import React from 'react'; export default function CustomShapeTemplate(employee, editEmployee, deleteEmployee) { const employeeName = employee ? employee.Full_Name : "Employee's Name"; const employeeTitle = employee ? employee.Title : "Employee's Title"; return ( <svg className="template"> <text className="template-name" x="50%" y="20%" > {employeeName} </text> <text className="template-title" x="50%" y="45%" > {employeeTitle} </text> <text className="template-button" x="40%" y="85%" onClick={editEmployee} > Edit </text> <text className="template-button" x="62%" y="85%" onClick={deleteEmployee} > Delete </text> </svg> ); }
import React from 'react'; export default function CustomShapeToolboxTemplate() { return ( <svg className="template"> <text x="50%" y="40%" > New </text> <text x="50%" y="70%" > Employee </text> </svg> ); }
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.js'; ReactDOM.render(<App />, document.getElementById('app'));
const employees = [ { ID: 1, Head_ID: 0, Full_Name: 'John Heart', Prefix: 'Mr.', Title: 'CEO', City: 'Los Angeles', State: 'California', Email: 'jheart@dx-email.com', Skype: 'jheart_DX_skype', Mobile_Phone: '(213) 555-9392', Birth_Date: '1964-03-16', Hire_Date: '1995-01-15', }, { ID: 2, Head_ID: 1, Full_Name: 'Samantha Bright', Prefix: 'Dr.', Title: 'COO', City: 'Los Angeles', State: 'California', Email: 'samanthab@dx-email.com', Skype: 'samanthab_DX_skype', Mobile_Phone: '(213) 555-2858', Birth_Date: '1966-05-02', Hire_Date: '2004-05-24', }, { ID: 3, Head_ID: 1, Full_Name: 'Arthur Miller', Prefix: 'Mr.', Title: 'CTO', City: 'Denver', State: 'Colorado', Email: 'arthurm@dx-email.com', Skype: 'arthurm_DX_skype', Mobile_Phone: '(310) 555-8583', Birth_Date: '1972-07-11', Hire_Date: '2007-12-18', }, { ID: 4, Head_ID: 1, Full_Name: 'Robert Reagan', Prefix: 'Mr.', Title: 'CMO', City: 'Bentonville', State: 'Arkansas', Email: 'robertr@dx-email.com', Skype: 'robertr_DX_skype', Mobile_Phone: '(818) 555-2387', Birth_Date: '1974-09-07', Hire_Date: '2002-11-08', }, { ID: 5, Head_ID: 1, Full_Name: 'Greta Sims', Prefix: 'Ms.', Title: 'HR Manager', City: 'Atlanta', State: 'Georgia', Email: 'gretas@dx-email.com', Skype: 'gretas_DX_skype', Mobile_Phone: '(818) 555-6546', Birth_Date: '1977-11-22', Hire_Date: '1998-04-23', }, { ID: 6, Head_ID: 3, Full_Name: 'Brett Wade', Prefix: 'Mr.', Title: 'IT Manager', City: 'Reno', State: 'Nevada', Email: 'brettw@dx-email.com', Skype: 'brettw_DX_skype', Mobile_Phone: '(626) 555-0358', Birth_Date: '1968-12-01', Hire_Date: '2009-03-06', }, { ID: 7, Head_ID: 5, Full_Name: 'Sandra Johnson', Prefix: 'Mrs.', Title: 'Controller', City: 'Beaver', State: 'Utah', Email: 'sandraj@dx-email.com', Skype: 'sandraj_DX_skype', Mobile_Phone: '(562) 555-2082', Birth_Date: '1974-11-15', Hire_Date: '2005-05-11', }, { ID: 8, Head_ID: 4, Full_Name: 'Ed Holmes', Prefix: 'Dr.', Title: 'Sales Manager', City: 'Malibu', State: 'California', Email: 'edwardh@dx-email.com', Skype: 'edwardh_DX_skype', Mobile_Phone: '(310) 555-1288', Birth_Date: '1973-07-14', Hire_Date: '2005-06-19', }, { ID: 9, Head_ID: 3, Full_Name: 'Barb Banks', Prefix: 'Mrs.', Title: 'Support Manager', City: 'Phoenix', State: 'Arizona', Email: 'barbarab@dx-email.com', Skype: 'barbarab_DX_skype', Mobile_Phone: '(310) 555-3355', Birth_Date: '1979-04-14', Hire_Date: '2002-08-07', }, { ID: 10, Head_ID: 2, Full_Name: 'Kevin Carter', Prefix: 'Mr.', Title: 'Shipping Manager', City: 'San Diego', State: 'California', Email: 'kevinc@dx-email.com', Skype: 'kevinc_DX_skype', Mobile_Phone: '(213) 555-2840', Birth_Date: '1978-01-09', Hire_Date: '2009-08-11', }, { ID: 11, Head_ID: 5, Full_Name: 'Cindy Stanwick', Prefix: 'Ms.', Title: 'HR Assistant', City: 'Little Rock', State: 'Arkansas', Email: 'cindys@dx-email.com', Skype: 'cindys_DX_skype', Mobile_Phone: '(818) 555-6655', Birth_Date: '1985-06-05', Hire_Date: '2008-03-24', }, { ID: 12, Head_ID: 8, Full_Name: 'Sammy Hill', Prefix: 'Mr.', Title: 'Sales Assistant', City: 'Pasadena', State: 'California', Email: 'sammyh@dx-email.com', Skype: 'sammyh_DX_skype', Mobile_Phone: '(626) 555-7292', Birth_Date: '1984-02-17', Hire_Date: '2012-02-01', }, { ID: 13, Head_ID: 10, Full_Name: 'Davey Jones', Prefix: 'Mr.', Title: 'Shipping Assistant', City: 'Pasadena', State: 'California', Email: 'davidj@dx-email.com', Skype: 'davidj_DX_skype', Mobile_Phone: '(626) 555-0281', Birth_Date: '1983-03-06', Hire_Date: '2011-04-24', }, { ID: 14, Head_ID: 10, Full_Name: 'Victor Norris', Prefix: 'Mr.', Title: 'Shipping Assistant', City: 'Little Rock', State: 'Arkansas', Email: 'victorn@dx-email.com', Skype: 'victorn_DX_skype', Mobile_Phone: '(213) 555-9278', Birth_Date: '1986-07-23', Hire_Date: '2012-07-23', }, { ID: 15, Head_ID: 10, Full_Name: 'Mary Stern', Prefix: 'Ms.', Title: 'Shipping Assistant', City: 'Beaver', State: 'Utah', Email: 'marys@dx-email.com', Skype: 'marys_DX_skype', Mobile_Phone: '(818) 555-7857', Birth_Date: '1982-04-08', Hire_Date: '2012-08-12', }, { ID: 16, Head_ID: 10, Full_Name: 'Robin Cosworth', Prefix: 'Mrs.', Title: 'Shipping Assistant', City: 'Los Angeles', State: 'California', Email: 'robinc@dx-email.com', Skype: 'robinc_DX_skype', Mobile_Phone: '(818) 555-0942', Birth_Date: '1981-06-12', Hire_Date: '2012-09-01', }, { ID: 17, Head_ID: 9, Full_Name: 'Kelly Rodriguez', Prefix: 'Ms.', Title: 'Support Assistant', City: 'Boise', State: 'Idaho', Email: 'kellyr@dx-email.com', Skype: 'kellyr_DX_skype', Mobile_Phone: '(818) 555-9248', Birth_Date: '1988-05-11', Hire_Date: '2012-10-13', }, { ID: 18, Head_ID: 9, Full_Name: 'James Anderson', Prefix: 'Mr.', Title: 'Support Assistant', City: 'Atlanta', State: 'Georgia', Email: 'jamesa@dx-email.com', Skype: 'jamesa_DX_skype', Mobile_Phone: '(323) 555-4702', Birth_Date: '1987-01-29', Hire_Date: '2012-10-18', }, { ID: 19, Head_ID: 9, Full_Name: 'Antony Remmen', Prefix: 'Mr.', Title: 'Support Assistant', City: 'Boise', State: 'Idaho', Email: 'anthonyr@dx-email.com', Skype: 'anthonyr_DX_skype', Mobile_Phone: '(310) 555-6625', Birth_Date: '1986-02-19', Hire_Date: '2013-01-19', }, { ID: 20, Head_ID: 8, Full_Name: 'Olivia Peyton', Prefix: 'Mrs.', Title: 'Sales Assistant', City: 'Atlanta', State: 'Georgia', Email: 'oliviap@dx-email.com', Skype: 'oliviap_DX_skype', Mobile_Phone: '(310) 555-2728', Birth_Date: '1981-06-03', Hire_Date: '2012-05-14', }, { ID: 21, Head_ID: 6, Full_Name: 'Taylor Riley', Prefix: 'Mr.', Title: 'Network Admin', City: 'San Jose', State: 'California', Email: 'taylorr@dx-email.com', Skype: 'taylorr_DX_skype', Mobile_Phone: '(310) 555-7276', Birth_Date: '1982-08-14', Hire_Date: '2012-04-14', }, { ID: 22, Head_ID: 6, Full_Name: 'Amelia Harper', Prefix: 'Mrs.', Title: 'Network Admin', City: 'Los Angeles', State: 'California', Email: 'ameliah@dx-email.com', Skype: 'ameliah_DX_skype', Mobile_Phone: '(213) 555-4276', Birth_Date: '1983-11-19', Hire_Date: '2011-02-10', }, { ID: 23, Head_ID: 6, Full_Name: 'Wally Hobbs', Prefix: 'Mr.', Title: 'Programmer', City: 'Chatsworth', State: 'California', Email: 'wallyh@dx-email.com', Skype: 'wallyh_DX_skype', Mobile_Phone: '(818) 555-8872', Birth_Date: '1984-12-24', Hire_Date: '2011-02-17', }, { ID: 24, Head_ID: 6, Full_Name: 'Brad Jameson', Prefix: 'Mr.', Title: 'Programmer', City: 'San Fernando', State: 'California', Email: 'bradleyj@dx-email.com', Skype: 'bradleyj_DX_skype', Mobile_Phone: '(818) 555-4646', Birth_Date: '1988-10-12', Hire_Date: '2011-03-02', }, { ID: 25, Head_ID: 6, Full_Name: 'Karen Goodson', Prefix: 'Miss', Title: 'Programmer', City: 'South Pasadena', State: 'California', Email: 'kareng@dx-email.com', Skype: 'kareng_DX_skype', Mobile_Phone: '(626) 555-0908', Birth_Date: '1987-04-26', Hire_Date: '2011-03-14', }, { ID: 26, Head_ID: 5, Full_Name: 'Marcus Orbison', Prefix: 'Mr.', Title: 'Travel Coordinator', City: 'Los Angeles', State: 'California', Email: 'marcuso@dx-email.com', Skype: 'marcuso_DX_skype', Mobile_Phone: '(213) 555-7098', Birth_Date: '1982-03-02', Hire_Date: '2005-05-19', }, ]; export default { getEmployees() { return employees; }, };
<!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.1.7/css/dx-diagram.min.css" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/24.1.7/css/dx.light.css" /> <link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.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>
#diagram { height: 725px; } #diagram .template .template-name { font-weight: bold; text-decoration: underline; } #diagram .template .template-title { font-style: italic; } #diagram .template .template-button { cursor: pointer; font-size: 8pt; fill: navy; } #diagram .template .template-button:hover { text-decoration: underline; } .dx-popup-content { padding: 0; } .dx-popup-content .dx-fieldset.buttons { display: flex; justify-content: flex-end; } .dx-popup-content .dx-fieldset.buttons > * { margin-left: 8px; }

The customDataExpr property links custom employee information from the data source to diagram nodes. Changes made to data are reflected in the diagram's history. Undo and redo actions (available within the control's UI) allow users to rollback/reapply changes.

The CustomShapeToolboxTemplate property specifies the template used for a shape within the toolbox.