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, { 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.12/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.