import React, { useCallback, useRef, useState } from 'react';
import Diagram, {
CustomShape, ContextToolbox, PropertiesPanel, Group, Tab, Toolbox, Nodes, AutoLayout, 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 = ['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<Diagram>(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 as any} />
</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, {
CustomShape,
ContextToolbox,
PropertiesPanel,
Group,
Tab,
Toolbox,
Nodes,
AutoLayout,
} 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@23.2.6/cjs',
'devextreme-react': 'npm:devextreme-react@23.2.6/cjs',
'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js',
'devextreme-quill': 'npm:devextreme-quill@1.6.4/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@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.4/standalone.js',
'prettier/parser-html': 'npm:prettier@2.8.4/parser-html.js',
},
packages: {
'devextreme': {
defaultExtension: 'js',
},
'devextreme-react': {
main: 'index.js',
},
'devextreme/events/utils': {
main: 'index',
},
'devextreme/localization/messages': {
format: 'json',
defaultExtension: '',
},
'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';
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>
<head>
<title>DevExtreme Demo</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/23.2.6/css/dx-diagram.min.css" />
<link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/23.2.6/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;
}