If you have technical questions, please create a support ticket in the DevExpress Support Center.
import React, { useCallback, useState } from 'react';
import Scheduler, { AppointmentDragging, SchedulerTypes } from 'devextreme-react/scheduler';
import Draggable, { DraggableTypes } from 'devextreme-react/draggable';
import ScrollView from 'devextreme-react/scroll-view';
import { Task, appointments as defaultAppointments, tasks as defaultTasks } from './data.ts';
const currentDate = new Date(2021, 3, 26);
const views: SchedulerTypes.Properties['views'] = [{ type: 'day', intervalCount: 3 }];
const draggingGroupName = 'appointmentsGroup';
const onListDragStart = (e: DraggableTypes.DragStartEvent) => {
e.cancel = true;
};
const onItemDragStart = (e: DraggableTypes.DragStartEvent) => {
e.itemData = e.fromData;
};
const onItemDragEnd = (e: DraggableTypes.DragEndEvent) => {
if (e.toData) {
e.cancel = true;
}
};
const App = () => {
const [state, setState] = useState({
tasks: defaultTasks, appointments: defaultAppointments,
});
const onAppointmentRemove = useCallback((e: SchedulerTypes.AppointmentDraggingRemoveEvent) => {
setState((currentState: { appointments: SchedulerTypes.Appointment[]; tasks: Task[]; }) => {
const { appointments, tasks } = currentState;
const index = appointments.indexOf(e.itemData);
if (index >= 0) {
tasks.push(e.itemData);
appointments.splice(index, 1);
}
return { appointments: [appointments], tasks: [tasks] };
});
}, []);
const onAppointmentAdd = useCallback((e: SchedulerTypes.AppointmentDraggingAddEvent) => {
setState((currentState: { appointments: SchedulerTypes.Appointment[]; tasks: Task[]; }) => {
const { appointments, tasks } = currentState;
const index = tasks.indexOf(e.fromData);
if (index >= 0) {
tasks.splice(index, 1);
appointments.push(e.itemData);
}
return { appointments: [appointments], tasks: [tasks] };
});
}, []);
return (
<React.Fragment>
<ScrollView id="scroll">
<Draggable
id="list"
data="dropArea"
group={draggingGroupName}
onDragStart={onListDragStart}>
{state.tasks.map((task) => (
<Draggable
key={task.text}
className="item dx-card"
clone={true}
group={draggingGroupName}
data={task}
onDragStart={onItemDragStart}
onDragEnd={onItemDragEnd}
>
{task.text}
</Draggable>
))}
</Draggable>
</ScrollView>
<Scheduler
id="scheduler"
timeZone="America/Los_Angeles"
dataSource={state.appointments}
views={views}
defaultCurrentDate={currentDate}
height={600}
startDayHour={9}
editing={true}
>
<AppointmentDragging
group={draggingGroupName}
onRemove={onAppointmentRemove as any}
onAdd={onAppointmentAdd as any}
/>
</Scheduler>
</React.Fragment>
);
};
export default App;
xxxxxxxxxx
import React, { useCallback, useState } from 'react';
import Scheduler, { AppointmentDragging } from 'devextreme-react/scheduler';
import Draggable from 'devextreme-react/draggable';
import ScrollView from 'devextreme-react/scroll-view';
import { appointments as defaultAppointments, tasks as defaultTasks } from './data.js';
const currentDate = new Date(2021, 3, 26);
const views = [{ type: 'day', intervalCount: 3 }];
const draggingGroupName = 'appointmentsGroup';
const onListDragStart = (e) => {
e.cancel = true;
};
const onItemDragStart = (e) => {
e.itemData = e.fromData;
};
const onItemDragEnd = (e) => {
if (e.toData) {
e.cancel = true;
}
};
const App = () => {
const [state, setState] = useState({
tasks: defaultTasks,
appointments: defaultAppointments,
});
const onAppointmentRemove = useCallback((e) => {
setState((currentState) => {
const { appointments, tasks } = currentState;
const index = appointments.indexOf(e.itemData);
if (index >= 0) {
tasks.push(e.itemData);
appointments.splice(index, 1);
}
return { appointments: [appointments], tasks: [tasks] };
});
}, []);
const onAppointmentAdd = useCallback((e) => {
setState((currentState) => {
const { appointments, tasks } = currentState;
const index = tasks.indexOf(e.fromData);
if (index >= 0) {
tasks.splice(index, 1);
appointments.push(e.itemData);
}
return { appointments: [appointments], tasks: [tasks] };
});
}, []);
return (
<React.Fragment>
<ScrollView id="scroll">
<Draggable
id="list"
data="dropArea"
group={draggingGroupName}
onDragStart={onListDragStart}
>
{state.tasks.map((task) => (
<Draggable
key={task.text}
className="item dx-card"
clone={true}
group={draggingGroupName}
data={task}
onDragStart={onItemDragStart}
onDragEnd={onItemDragEnd}
>
{task.text}
</Draggable>
))}
</Draggable>
</ScrollView>
<Scheduler
id="scheduler"
timeZone="America/Los_Angeles"
dataSource={state.appointments}
views={views}
defaultCurrentDate={currentDate}
height={600}
startDayHour={9}
editing={true}
>
<AppointmentDragging
group={draggingGroupName}
onRemove={onAppointmentRemove}
onAdd={onAppointmentAdd}
/>
</Scheduler>
</React.Fragment>
);
};
export default App;
xxxxxxxxxx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.tsx';
ReactDOM.render(
<App />,
document.getElementById('app'),
);
xxxxxxxxxx
import { SchedulerTypes } from 'devextreme-react/scheduler';
export type Task = {
text: string;
};
export const tasks: Task[] = [
{
text: 'New Brochures',
}, {
text: 'Brochure Design Review',
}, {
text: 'Upgrade Personal Computers',
}, {
text: 'Install New Router in Dev Room',
}, {
text: 'Upgrade Server Hardware',
}, {
text: 'Install New Database',
}, {
text: 'Website Re-Design Plan',
}, {
text: 'Create Icons for Website',
}, {
text: 'Submit New Website Design',
}, {
text: 'Launch New Website',
},
];
export const appointments: SchedulerTypes.Appointment[] = [
{
text: 'Book Flights to San Fran for Sales Trip',
startDate: new Date('2021-04-26T19:00:00.000Z'),
endDate: new Date('2021-04-26T20:00:00.000Z'),
allDay: true,
}, {
text: 'Approve Personal Computer Upgrade Plan',
startDate: new Date('2021-04-27T17:00:00.000Z'),
endDate: new Date('2021-04-27T18:00:00.000Z'),
}, {
text: 'Final Budget Review',
startDate: new Date('2021-04-27T19:00:00.000Z'),
endDate: new Date('2021-04-27T20:35:00.000Z'),
}, {
text: 'Approve New Online Marketing Strategy',
startDate: new Date('2021-04-28T19:00:00.000Z'),
endDate: new Date('2021-04-28T21:00:00.000Z'),
}, {
text: 'Customer Workshop',
startDate: new Date('2021-04-29T18:00:00.000Z'),
endDate: new Date('2021-04-29T19:00:00.000Z'),
allDay: true,
}, {
text: 'Prepare 2021 Marketing Plan',
startDate: new Date('2021-04-29T18:00:00.000Z'),
endDate: new Date('2021-04-29T20:30:00.000Z'),
},
];
xxxxxxxxxx
window.exports = window.exports || {};
window.config = {
transpiler: 'ts',
typescriptOptions: {
module: 'system',
emitDecoratorMetadata: true,
experimentalDecorators: true,
jsx: 'react',
},
meta: {
'react': {
'esModule': true,
},
'typescript': {
'exports': 'ts',
},
'devextreme/time_zone_utils.js': {
'esModule': true,
},
'devextreme/localization.js': {
'esModule': true,
},
'devextreme/viz/palette.js': {
'esModule': true,
},
'openai': {
'esModule': true,
},
},
paths: {
'npm:': 'https://unpkg.com/',
'bundles:': 'bundles/',
'externals:': 'bundles/externals/',
},
defaultExtension: 'js',
map: {
'ts': 'npm:plugin-typescript@8.0.0/lib/plugin.js',
'typescript': 'npm:typescript@4.2.4/lib/typescript.js',
'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js',
'react': 'npm:react@17.0.2/umd/react.development.js',
'react-dom': 'npm:react-dom@17.0.2/umd/react-dom.development.js',
'prop-types': 'npm:prop-types/prop-types.js',
'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js',
'luxon': 'npm:luxon@3.4.4/build/global/luxon.min.js',
'es6-object-assign': 'npm:es6-object-assign',
'devextreme': 'npm:devextreme@link:../../packages/devextreme/artifacts/npm/devextreme/cjs',
'devextreme-react': 'npm:devextreme-react@link:../../packages/devextreme-react/npm/cjs',
'devextreme-quill': 'npm:devextreme-quill@1.7.1/dist/dx-quill.min.js',
'devexpress-diagram': 'npm:devexpress-diagram@2.2.5/dist/dx-diagram.js',
'devexpress-gantt': 'npm:devexpress-gantt@4.1.54/dist/dx-gantt.js',
'@devextreme/runtime': 'npm:@devextreme/runtime@3.0.12',
'inferno': 'npm:inferno@7.4.11/dist/inferno.min.js',
'inferno-compat': 'npm:inferno-compat/dist/inferno-compat.min.js',
'inferno-create-element': 'npm:inferno-create-element@7.4.11/dist/inferno-create-element.min.js',
'inferno-dom': 'npm:inferno-dom/dist/inferno-dom.min.js',
'inferno-hydrate': 'npm:inferno-hydrate/dist/inferno-hydrate.min.js',
'inferno-clone-vnode': 'npm:inferno-clone-vnode/dist/inferno-clone-vnode.min.js',
'inferno-create-class': 'npm:inferno-create-class/dist/inferno-create-class.min.js',
'inferno-extras': 'npm:inferno-extras/dist/inferno-extras.min.js',
'devextreme-cldr-data': 'npm:devextreme-cldr-data@1.0.3',
// SystemJS plugins
'plugin-babel': 'npm:systemjs-plugin-babel@0.0.25/plugin-babel.js',
'systemjs-babel-build': 'npm:systemjs-plugin-babel@0.0.25/systemjs-babel-browser.js',
// Prettier
'prettier/standalone': 'npm:prettier@2.8.8/standalone.js',
'prettier/parser-html': 'npm:prettier@2.8.8/parser-html.js',
},
packages: {
'devextreme': {
defaultExtension: 'js',
},
'devextreme-react': {
main: 'index.js',
},
'devextreme/events/utils': {
main: 'index',
},
'devextreme/localization/messages': {
format: 'json',
defaultExtension: 'json',
},
'devextreme/events': {
main: 'index',
},
'es6-object-assign': {
main: './index.js',
defaultExtension: 'js',
},
},
packageConfigPaths: [
'npm:@devextreme/*/package.json',
'npm:@devextreme/runtime@3.0.12/inferno/package.json',
],
babelOptions: {
sourceMaps: false,
stage0: true,
react: true,
},
};
System.config(window.config);
xxxxxxxxxx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.js';
ReactDOM.render(<App />, document.getElementById('app'));
xxxxxxxxxx
export const tasks = [
{
text: 'New Brochures',
},
{
text: 'Brochure Design Review',
},
{
text: 'Upgrade Personal Computers',
},
{
text: 'Install New Router in Dev Room',
},
{
text: 'Upgrade Server Hardware',
},
{
text: 'Install New Database',
},
{
text: 'Website Re-Design Plan',
},
{
text: 'Create Icons for Website',
},
{
text: 'Submit New Website Design',
},
{
text: 'Launch New Website',
},
];
export const appointments = [
{
text: 'Book Flights to San Fran for Sales Trip',
startDate: new Date('2021-04-26T19:00:00.000Z'),
endDate: new Date('2021-04-26T20:00:00.000Z'),
allDay: true,
},
{
text: 'Approve Personal Computer Upgrade Plan',
startDate: new Date('2021-04-27T17:00:00.000Z'),
endDate: new Date('2021-04-27T18:00:00.000Z'),
},
{
text: 'Final Budget Review',
startDate: new Date('2021-04-27T19:00:00.000Z'),
endDate: new Date('2021-04-27T20:35:00.000Z'),
},
{
text: 'Approve New Online Marketing Strategy',
startDate: new Date('2021-04-28T19:00:00.000Z'),
endDate: new Date('2021-04-28T21:00:00.000Z'),
},
{
text: 'Customer Workshop',
startDate: new Date('2021-04-29T18:00:00.000Z'),
endDate: new Date('2021-04-29T19:00:00.000Z'),
allDay: true,
},
{
text: 'Prepare 2021 Marketing Plan',
startDate: new Date('2021-04-29T18:00:00.000Z'),
endDate: new Date('2021-04-29T20:30:00.000Z'),
},
];
xxxxxxxxxx
<html lang="en">
<head></head>
<body class="dx-viewport">
<div class="demo-container">
<div id="app"></div>
</div>
</body>
</html>
xxxxxxxxxx
#scroll,
#list {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 240px;
}
.item {
color: var(--dx-color-text);
background-color: var(--dx-component-color-bg);
box-sizing: border-box;
padding: 10px 20px;
margin-bottom: 10px;
}
#scheduler {
margin-left: 270px;
}
.dx-draggable-source {
opacity: 0.5;
}
.dx-draggable-dragging > * {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 6px 8px rgba(0, 0, 0, 0.2);
}
Follow the steps below to implement this functionality:
-
Configure the Scheduler
In the appointmentDragging object, implement the onAdd function (in which you should add an appointment) and the onRemove function (in which you should delete an appointment and create a corresponding list item). -
Configure list items
Attach an instance of the Draggable component to every list item. The component has the data property that can contain custom data. In this demo, it is an appointment's subject. Implement the onDragStart function in which you should pass the subject to the Scheduler where it is used to add new appointments. -
Configure the list
Attach another Draggable instance to the list which only serves as the drop target. Implement the onDragStart function to ensure the list cannot be dragged. -
Add the controls to the same group
To enable drag and drop operations between the controls, assign the same value to the group property of the Scheduler's appointmentDragging object and both Draggable components.