---
title: "React Event calendar Drag and Drop from External Lists Example | Mobiscroll"
component: "Event calendar"
framework: "React"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/react/eventcalendar/external-drag-drop-sortable-dragula"
---

# Drag and Drop from External Lists - React Event calendar demo

Drag items from external SortableJS or Dragula lists onto a React event calendar to schedule events — or drag them out to unschedule. For React and Next.JS.

- [Live demo](https://demo.mobiscroll.com/react/eventcalendar/external-drag-drop-sortable-dragula)
- [View React JS code on GitHub](https://github.com/acidb/mobiscroll-demos-react/tree/main/src/demos/eventcalendar/calendar-view/external-drag-drop-sortable-dragula)
- [View React TS code on GitHub](https://github.com/acidb/mobiscroll-demos-react-ts/tree/main/src/demos/eventcalendar/calendar-view/external-drag-drop-sortable-dragula)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/javascript/eventcalendar/external-drag-drop-sortable-dragula.md), [jQuery](https://demo.mobiscroll.com/jquery/eventcalendar/external-drag-drop-sortable-dragula.md), [Angular](https://demo.mobiscroll.com/angular/eventcalendar/external-drag-drop-sortable-dragula.md), [Vue](https://demo.mobiscroll.com/vue/eventcalendar/external-drag-drop-sortable-dragula.md)

## Description

**Dragging events on the Mobiscroll event calendar**

Events can be scheduled by dragging and dropping an external item onto the event calendar with Mobiscroll `draggable`. In order for that to work you will need to have two things set up:

- Enable the event calendar to receive external events by setting [`externalDrop`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-externalDrop) to `true`.
- Initialize the external events (containers) as `draggable` components.

Use the `Draggable` component to specify a skeleton event through its `dragData` option and reference the draggable container in the `element` option. The `dragData` accepts a full event definition that will be added to the event calendar on drop. If omitted, a default event will be created.

**Working together with SortableJS and Dragula lists**

Mobiscroll provides built-in support for two widely used reorderable drag-and-drop list libraries: [SortableJS](https://sortablejs.github.io/Sortable/) and [Dragula](https://bevacqua.github.io/dragula/). If you are already using any of these libraries to reorder list items, they can be seamlessly integrated with the event calendar. This integration enables a smooth workflow where items can be dragged from external lists and scheduled directly onto the calendar.

Events can be created and scheduled by dragging items from third-party lists into the event calendar. To enable this behavior you will need to:

- Allow the event calendar to receive external drops by setting [`externalDrop`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-externalDrop) to `true`.
- Use the `sortableJsDraggable` or `dragulaDraggable` plugins to connect the third-party list with the scheduler. The event data will be read from the dragged element's `data-drag-data` attribute, or the plugin's options expose an `eventData` function where the event definition (title, start, end, resource, etc.) can be specified.

You can then handle the calendars lifecycle events such as [`onEventCreate`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventCreate), [`onEventCreated`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventCreated) or [`onEventCreateFailed`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventCreateFailed) to run custom logic (e.g. showing a toast) when a drop succeeds or fails.

**Unscheduling events (or dragging them off a calendar)**

Additionally events can be unscheduled by dragging them out from the calendar and dropping them onto an external drop container. To activate this, you will need to:

- Enable the calendar to allow dragging events out by setting [`externalDrag`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-externalDrag) to `true`.

- Initialize the [`dropcontainer`](https://mobiscroll.com/docs/react/eventcalendar/drag-and-drop#dropcontainer).

Use the [`dropcontainer`](https://mobiscroll.com/docs/react/eventcalendar/drag-and-drop#dropcontainer) component and its [`onItemDrop`](https://mobiscroll.com/docs/react/eventcalendar/drag-and-drop#event-onItemDrop) event to handle dropped events.

Events can also be dropped on another event calendar with `externalDrop` enabled.

When an event is dropped into an external drop container or another calendar, it will be deleted from the original calendar and the [`onEventDelete`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventDelete) will be fired.

You can also use the [`onEventDragLeave`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventDragLeave) and [`onEventDragEnter`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventDragEnter), and the drop container's `onItemDragEnter` and `onItemDragLeave` events to provide visual feedback or running custom logic during drag.

Learn more from the [external drag & drop documentation](https://mobiscroll.com/docs/react/eventcalendar/drag-and-drop#the-eventcalendar-as-source).

**Unscheduling events to SortableJS and Dragula lists**

Dropping events from the Eventcalendar to [SortableJS](https://sortablejs.github.io/Sortable/) and [Dragula](https://bevacqua.github.io/dragula/) lists is also possible with the `sortableJsDraggable` and `dragulaDraggable` plugins. To enable this behavior you will need to set the following under the `options` configuration:

- Allow the list to receive external drops by setting `externalDrop` to `true`
- Use the `onExternalDrop` callback to update the content of the third-party list. The function arguments return the `afterElement` - the list element before which the clone is dropped, `container` - the list container, `dragData` - the dragged event data, `position` - the index where the clone is dropped on the list.

For details, and option lists see the [Third-party dragging support](https://mobiscroll.com/docs/react/eventcalendar/drag-and-drop#third-party-dragging-support) section in the documentation

**Looking for external drag into a scheduler?** [Check out this example →](https://demo.mobiscroll.com/react/scheduler/external-drag-drop-sortable-dragula)

## What this demo shows

- This demo shows a desktop month view event calendar with a separate external event panel for scheduling and unscheduling events by drag and drop.
- **Calendar header** The header shows the current month and year on the left, and blue month navigation arrows with a `Today` button between them on the right.
- **External lists** A scrollable panel on the right contains three external event lists: `Mobiscroll draggable` with four red items, `SortableJS list (externally sortable)` with four orange items, and `Dragula list (externally sortable)` with four green items.
- **Scheduling from lists** Any item from the external lists can be dragged onto the month view to create and schedule that event on the target day.
- **Unscheduling back to lists** Scheduled events can be dragged out of the calendar and dropped back into an external list, removing them from the month view.
- **List behavior** When an item is scheduled from an external list, it disappears from that list and appears on the calendar.
- **Feedback on drop** After an event is scheduled from an external list, a toast appears near the bottom center of the calendar showing the event title and the `added` message.
- **Feedback on external list drop** After an event is unscheduled from the calendar and dropped on the external list a toast appears near the bottom center of the calendar showing the event title and the `unscheduled` message.
- **Event label rendering** Day cells display event labels with different visual styles for all-day, multi-day, and time-specific events.
- **Overflow handling** The number of visible event labels in a day cell depends on the available vertical space.
- **More events popup** When not all events fit in a day cell, an `X more` label appears; selecting it opens a popup that lists all events of that day.
- **Event interaction** Event labels are highlighted on hover and on selection.
- **Day cell states** Hovering a day cell highlights the day number with a gray background, while clicking the empty part of the cell selects the day and highlights the day number with a blue background.
- **Drag to create** New events can be created directly on the calendar by clicking and dragging across the empty space of the day cells.

## Best for

- **Unscheduled work queues** Managing tasks, appointments, or work items that start in an external backlog and need to be placed onto calendar dates.
- **Desktop planning workflows** Month-based planning experiences where users need a broad date overview together with a side panel of draggable items.
- **Schedule and unschedule flows** Use cases where users need to move items onto the calendar to assign dates and move them back out to return them to an unscheduled state.
- **Third-party drag-and-drop integration** Projects that need to combine Mobiscroll Eventcalendar with existing `SortableJS` or `Dragula` list implementations.

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-themeVariant) - option
- [`data`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-data) - option
- [`labels`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-labels) - option
- [`externalDrop`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-externalDrop) - option
- [`clickToCreate`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-clickToCreate) - option
- [`dragToCreate`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-dragToCreate) - option
- [`dragToMove`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-dragToMove) - option
- [`dragToResize`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-dragToResize) - option
- [`eventDelete`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-eventDelete) - option
- [`view`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-view) - option
- [`externalDrag`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-externalDrag) - option
- [`onEventCreated`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventCreated) - event
- [`onEventDeleted`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventDeleted) - event
- [`onClose`](https://mobiscroll.com/docs/react/notifications/toast#event-onClose) - event

## Code

### JSX

```jsx
import {
  Draggable,
  dragulaDraggable,
  Dropcontainer,
  Eventcalendar,
  getJson,
  setOptions,
  sortableJsDraggable,
  Toast,
} from '@mobiscroll/react';
import dragula from 'dragula';
import PropTypes from 'prop-types';
import { useCallback, useEffect, useMemo, useState } from 'react';
import Sortable from 'sortablejs';
import 'dragula/dist/dragula.css';

setOptions({
  theme: 'ios',
  themeVariant: 'light'
});

function Task({ data, isDraggable }) {
  const [draggable, setDraggable] = useState();

  const setDragElm = useCallback((elm) => {
    setDraggable(elm);
  }, []);

  const eventLength = Math.round(Math.abs(new Date(data.end).getTime() - new Date(data.start).getTime()) / (60 * 60 * 1000));

  return (
    <div
      className="mds-drag-drop-sort-task"
      ref={isDraggable && setDragElm}
      style={{ background: data.color }}
      data-drag-data={!isDraggable && JSON.stringify(data)}
    >
      <div>{data.title}</div>
      <div>{eventLength + ' hour' + (eventLength > 1 ? 's' : '')}</div>
      {isDraggable && <Draggable dragData={data} element={draggable} />}
    </div>
  );
}

Task.propTypes = {
  data: PropTypes.object.isRequired,
  isDraggable: PropTypes.bool,
};

function App() {
  const [dropCont, setDropCont] = useState();
  const [sortableCont, setSortableCont] = useState();
  const [dragulaCont, setDragulaCont] = useState();
  const [myEvents, setEvents] = useState([]);
  const [isToastOpen, setToastOpen] = useState(false);
  const [toastMessage, setToastMessage] = useState();

  const [myDraggableTasks, setDraggableTasks] = useState([
    {
      id: 1,
      title: 'Task 1',
      color: '#cf4343',
      start: '2026-10-26T08:00',
      end: '2026-10-26T09:30',
    },
    {
      id: 2,
      title: 'Task 2',
      color: '#cf4343',
      start: '2026-10-26T08:00',
      end: '2026-10-26T10:00',
    },
    {
      id: 3,
      title: 'Task 3',
      color: '#cf4343',
      start: '2026-10-26T10:00',
      end: '2026-10-26T14:00',
    },
    {
      id: 4,
      title: 'Task 4',
      color: '#cf4343',
      start: '2026-10-26T12:00',
      end: '2026-10-26T18:00',
    },
  ]);

  const [mySortableTasks, setSortableTasks] = useState([
    {
      id: 'sortable-1',
      title: 'Task 5',
      color: '#e49516',
      start: '2026-10-26T08:00',
      end: '2026-10-26T09:30',
    },
    {
      id: 'sortable-2',
      title: 'Task 6',
      color: '#e49516',
      start: '2026-10-26T12:00',
      end: '2026-10-26T15:00',
    },
    {
      id: 'sortable-3',
      title: 'Task 7',
      color: '#e49516',
      start: '2026-10-26T08:30',
      end: '2026-10-26T11:00',
    },
    {
      id: 'sortable-4',
      title: 'Task 8',
      color: '#e49516',
      start: '2026-10-26T16:00',
      end: '2026-10-26T17:00',
    },
  ]);

  const [myDragulaTasks, setDragulaTasks] = useState([
    {
      id: 'dragula-1',
      title: 'Task 9',
      color: '#1ca11a',
      start: '2026-10-26T08:00',
      end: '2026-10-26T09:30',
    },
    {
      id: 'dragula-2',
      title: 'Task 10',
      color: '#1ca11a',
      start: '2026-10-26T12:00',
      end: '2026-10-26T15:00',
    },
    {
      id: 'dragula-3',
      title: 'Task 11',
      color: '#1ca11a',
      start: '2026-10-26T08:30',
      end: '2026-10-26T11:00',
    },
    {
      id: 'dragula-4',
      title: 'Task 12',
      color: '#1ca11a',
      start: '2026-10-26T16:00',
      end: '2026-10-26T17:00',
    },
  ]);

  const myView = useMemo(() => ({ calendar: { labels: true } }), []);

  const handleEventCreated = useCallback((args) => {
    if (args.action === 'externalDrop') {
      setDraggableTasks((tasks) => tasks.filter((item) => item.id !== args.event.id));
      setSortableTasks((tasks) => tasks.filter((item) => item.id !== args.event.id));
      setDragulaTasks((tasks) => tasks.filter((item) => item.id !== args.event.id));
      setToastMessage(args.event.title + ' added');
      setToastOpen(true);
    }
  }, []);

  const handleEventDeleted = useCallback((args) => {
    setToastMessage(args.event.title + ' unscheduled');
    setToastOpen(true);
  }, []);

  const handleItemDrop = useCallback((args) => {
    if (args.data) {
      setDraggableTasks((tasks) => [...tasks, args.data]);
    }
  }, []);

  const handleToastClose = useCallback(() => {
    setToastOpen(false);
  }, []);

  useEffect(() => {
    let sortableInstance;
    if (sortableCont) {
      sortableInstance = new Sortable(sortableCont, {
        animation: 150,
        forceFallback: true,
      });

      sortableJsDraggable.init(sortableInstance, {
        cloneSelector: '.sortable-drag',
        externalDrop: true,
        onExternalDrop: (a) => {
          const dragData = a.dragData;
          setSortableTasks((prev) => {
            const newTasks = [...prev];
            newTasks.splice(a.position, 0, dragData);
            return newTasks;
          });
        },
      });
    }

    let drake;
    if (dragulaCont) {
      drake = dragula([dragulaCont]);
      dragulaDraggable.init(drake, {
        externalDrop: true,
        onExternalDrop: (a) => {
          const dragData = a.dragData;
          setDragulaTasks((prev) => {
            const newTasks = [...prev];
            newTasks.splice(a.position, 0, dragData);
            return newTasks;
          });
        },
      });
    }

    return () => {
      if (sortableInstance) {
        sortableInstance.destroy();
      }
      if (drake) {
        drake.destroy();
      }
    };
  }, [dragulaCont, sortableCont]);

  useEffect(() => {
    getJson(
      'https://trial.mobiscroll.com/drag-drop-events/',
      (events) => {
        setEvents(events);
      },
      'jsonp',
    );
  }, []);

  return (
    <div className="mbsc-grid mbsc-no-padding mds-full-height">
      <div className="mbsc-row mds-full-height">
        <div className="mbsc-col-sm-9 mds-drag-drop-sort-calendar mds-full-height">
          <Eventcalendar
            clickToCreate={true}
            dragToCreate={true}
            dragToMove={true}
            dragToResize={true}
            eventDelete={true}
            data={myEvents}
            view={myView}
            externalDrop={true}
            dragToMove={true}
            dragToCreate={true}
            externalDrag={true}
            onEventCreated={handleEventCreated}
            onEventDeleted={handleEventDeleted}
          />
        </div>
        <div className="mbsc-col-sm-3 mds-drag-drop-sort-container-wrapper mds-full-height">
          <div className="mbsc-txt-muted mds-third-party-title">Mobiscroll draggable</div>
          <div className="mds-drag-drop-sort-container" ref={setDropCont}>
            <Dropcontainer onItemDrop={handleItemDrop} element={dropCont}>
              {myDraggableTasks.map((task) => (
                <Task key={task.id} data={task} isDraggable={true} />
              ))}
            </Dropcontainer>
          </div>
          <div className="mbsc-txt-muted mds-third-party-title">SortableJS list (externally sortable)</div>
          <div className="mds-drag-drop-sort-container" ref={setSortableCont}>
            {mySortableTasks.map((task) => (
              <Task key={task.id} data={task} />
            ))}
          </div>
          <div className="mbsc-txt-muted mds-third-party-title">Dragula list (externally sortable)</div>
          <div className="mds-drag-drop-sort-container" ref={setDragulaCont}>
            {myDragulaTasks.map((task) => (
              <Task key={task.id} data={task} />
            ))}
          </div>
        </div>
      </div>
      <Toast message={toastMessage} isOpen={isToastOpen} onClose={handleToastClose} />
    </div>
  );
}

export default App;
```

### TSX

```tsx
import {
  Draggable,
  dragulaDraggable,
  Dropcontainer,
  Eventcalendar,
  getJson,
  MbscCalendarEvent,
  MbscEventcalendarView,
  MbscEventCreatedEvent,
  MbscEventDeletedEvent,
  MbscExternalDropEvent,
  MbscItemDragEvent,
  setOptions,
  sortableJsDraggable,
  Toast,
} from '@mobiscroll/react';
import dragula, { Drake } from 'dragula';
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import Sortable from 'sortablejs';
import 'dragula/dist/dragula.css';
setOptions({
  theme: 'ios',
  themeVariant: 'light'
});

const Task = (props: { data: MbscCalendarEvent; isDraggable?: boolean }) => {
  const data = props.data;
  const isDraggable = props.isDraggable;
  const [draggable, setDraggable] = useState<HTMLDivElement>();

  const setDragElm = useCallback((elm: HTMLDivElement) => {
    setDraggable(elm);
  }, []);

  const eventLength = Math.round(
    Math.abs(new Date(data.end as string).getTime() - new Date(data.start as string).getTime()) / (60 * 60 * 1000),
  );

  return (
    <div
      className="mds-drag-drop-sort-task"
      ref={isDraggable ? setDragElm : null}
      style={{ background: data.color }}
      data-drag-data={!isDraggable && JSON.stringify(data)}
    >
      <div>{data.title}</div>
      <div>{eventLength + ' hour' + (eventLength > 1 ? 's' : '')}</div>
      {isDraggable && <Draggable dragData={data} element={draggable} />}
    </div>
  );
};

const App: FC = () => {
  const [dropCont, setDropCont] = useState<HTMLDivElement | null>();
  const [sortableCont, setSortableCont] = useState<HTMLDivElement | null>();
  const [dragulaCont, setDragulaCont] = useState<HTMLDivElement | null>();
  const [myEvents, setEvents] = useState<MbscCalendarEvent[]>([]);
  const [isToastOpen, setToastOpen] = useState<boolean>(false);
  const [toastMessage, setToastMessage] = useState<string>('');

  const [myDraggableTasks, setDraggableTasks] = useState<MbscCalendarEvent[]>([
    {
      id: 1,
      title: 'Task 1',
      color: '#cf4343',
      start: '2026-10-26T08:00',
      end: '2026-10-26T09:30',
    },
    {
      id: 2,
      title: 'Task 2',
      color: '#cf4343',
      start: '2026-10-26T08:00',
      end: '2026-10-26T10:00',
    },
    {
      id: 3,
      title: 'Task 3',
      color: '#cf4343',
      start: '2026-10-26T10:00',
      end: '2026-10-26T14:00',
    },
    {
      id: 4,
      title: 'Task 4',
      color: '#cf4343',
      start: '2026-10-26T12:00',
      end: '2026-10-26T18:00',
    },
  ]);

  const [mySortableTasks, setSortableTasks] = useState<MbscCalendarEvent[]>([
    {
      id: 'sortable-1',
      title: 'Task 5',
      color: '#e49516',
      start: '2026-10-26T08:00',
      end: '2026-10-26T09:30',
    },
    {
      id: 'sortable-2',
      title: 'Task 6',
      color: '#e49516',
      start: '2026-10-26T12:00',
      end: '2026-10-26T15:00',
    },
    {
      id: 'sortable-3',
      title: 'Task 7',
      color: '#e49516',
      start: '2026-10-26T08:30',
      end: '2026-10-26T11:00',
    },
    {
      id: 'sortable-4',
      title: 'Task 8',
      color: '#e49516',
      start: '2026-10-26T16:00',
      end: '2026-10-26T17:00',
    },
  ]);

  const [myDragulaTasks, setDragulaTasks] = useState<MbscCalendarEvent[]>([
    {
      id: 'dragula-1',
      title: 'Task 9',
      color: '#1ca11a',
      start: '2026-10-26T08:00',
      end: '2026-10-26T09:30',
    },
    {
      id: 'dragula-2',
      title: 'Task 10',
      color: '#1ca11a',
      start: '2026-10-26T12:00',
      end: '2026-10-26T15:00',
    },
    {
      id: 'dragula-3',
      title: 'Task 11',
      color: '#1ca11a',
      start: '2026-10-26T08:30',
      end: '2026-10-26T11:00',
    },
    {
      id: 'dragula-4',
      title: 'Task 12',
      color: '#1ca11a',
      start: '2026-10-26T16:00',
      end: '2026-10-26T17:00',
    },
  ]);

  const myView = useMemo<MbscEventcalendarView>(() => ({ calendar: { labels: true } }), []);

  const handleEventCreated = useCallback((args: MbscEventCreatedEvent) => {
    if (args.action === 'externalDrop') {
      setDraggableTasks((tasks) => tasks.filter((item) => item.id !== args.event.id));
      setSortableTasks((tasks) => tasks.filter((item) => item.id !== args.event.id));
      setDragulaTasks((tasks) => tasks.filter((item) => item.id !== args.event.id));
      setToastMessage(args.event.title + ' added');
      setToastOpen(true);
    }
  }, []);

  const handleEventDeleted = useCallback((args: MbscEventDeletedEvent) => {
    setToastMessage(args.event.title + ' unscheduled');
    setToastOpen(true);
  }, []);

  const handleItemDrop = useCallback((args: MbscItemDragEvent) => {
    if (args.data) {
      setDraggableTasks((tasks) => [...tasks, args.data]);
    }
  }, []);

  const handleToastClose = useCallback(() => {
    setToastOpen(false);
  }, []);

  useEffect(() => {
    let sortableInstance: Sortable;
    if (sortableCont) {
      sortableInstance = new Sortable(sortableCont, {
        animation: 150,
        forceFallback: true,
      });

      sortableJsDraggable.init(sortableInstance, {
        cloneSelector: '.sortable-drag',
        externalDrop: true,
        onExternalDrop: (a: MbscExternalDropEvent) => {
          const dragData = a.dragData;
          setSortableTasks((prev) => {
            const newTasks = [...prev];
            newTasks.splice(a.position, 0, dragData);
            return newTasks;
          });
        },
      });
    }

    let drake: Drake;
    if (dragulaCont) {
      drake = dragula([dragulaCont]);
      dragulaDraggable.init(drake, {
        externalDrop: true,
        onExternalDrop: (a: MbscExternalDropEvent) => {
          const dragData = a.dragData;
          setDragulaTasks((prev) => {
            const newTasks = [...prev];
            newTasks.splice(a.position, 0, dragData);
            return newTasks;
          });
        },
      });
    }

    return () => {
      if (sortableInstance) {
        sortableInstance.destroy();
      }
      if (drake) {
        drake.destroy();
      }
    };
  }, [dragulaCont, sortableCont]);

  useEffect(() => {
    getJson(
      'https://trial.mobiscroll.com/drag-drop-events/',
      (events) => {
        setEvents(events);
      },
      'jsonp',
    );
  }, []);

  return (
    <div className="mbsc-grid mbsc-no-padding mds-full-height">
      <div className="mbsc-row mds-full-height">
        <div className="mbsc-col-sm-9 mds-drag-drop-sort-calendar mds-full-height">
          <Eventcalendar
            clickToCreate={true}
            dragToCreate={true}
            dragToMove={true}
            dragToResize={true}
            eventDelete={true}
            data={myEvents}
            view={myView}
            externalDrop={true}
            dragToMove={true}
            dragToCreate={true}
            externalDrag={true}
            onEventCreated={handleEventCreated}
            onEventDeleted={handleEventDeleted}
          />
        </div>
        <div className="mbsc-col-sm-3 mds-drag-drop-sort-container-wrapper mds-full-height">
          <div className="mbsc-txt-muted mds-third-party-title">Mobiscroll draggable</div>
          <div className="mds-drag-drop-sort-container" ref={setDropCont}>
            <Dropcontainer onItemDrop={handleItemDrop} element={dropCont}>
              {myDraggableTasks.map((task) => (
                <Task key={task.id} data={task} isDraggable={true} />
              ))}
            </Dropcontainer>
          </div>
          <div className="mbsc-txt-muted mds-third-party-title">SortableJS list (externally sortable)</div>
          <div className="mds-drag-drop-sort-container" ref={setSortableCont}>
            {mySortableTasks.map((task) => (
              <Task key={task.id} data={task} />
            ))}
          </div>
          <div className="mbsc-txt-muted mds-third-party-title">Dragula list (externally sortable)</div>
          <div className="mds-drag-drop-sort-container" ref={setDragulaCont}>
            {myDragulaTasks.map((task) => (
              <Task key={task.id} data={task} />
            ))}
          </div>
        </div>
      </div>
      <Toast message={toastMessage} isOpen={isToastOpen} onClose={handleToastClose} />
    </div>
  );
};

export default App;
```

### CSS

```css
.mds-full-height {
  height: 100%;
}

.mds-drag-drop-sort-calendar {
  border-right: 1px solid #ccc;
}

.mds-drag-drop-sort-container {
  margin: 10px;
}

.mds-drag-drop-sort-container-wrapper {
  overflow: auto;
}

.mds-third-party-title {
  margin-top: 12px;
  padding: 0 20px;
  font-size: 16px;
  font-weight: 600;
}

.mds-drag-drop-sort-task {
  margin: 10px;
  padding: 10px;
  touch-action: none;
  color: #fff;
  border-radius: 8px;
  font-family:
    -apple-system,
    Segoe UI,
    Roboto,
    sans-serif;
}
```

## Implementation notes

- Set `view: { calendar: { labels: true } }`. Enable `externalDrop: true` and `externalDrag: true` to allow events to be dropped in from and dragged out to external lists. Also set `dragToMove: true` and `dragToCreate: true`.
- **Mobiscroll draggable list**: initialize each task item with the `Draggable` component and `dragData` set to the event object (Angular: `mbsc-draggable` directive with `[dragData]`; JS/jQuery: `mobiscroll.draggable(element, { dragData })` or the `mbsc-draggable` attribute with `data-drag-data`). Wrap the list container with a `Dropcontainer` and handle `onItemDrop` (Vue: `@item-drop`) to re-add events that are dragged back from the calendar - `args.data` carries the event object.
- **SortableJS list**: initialize `new Sortable(container, options)`, then call `sortableJsDraggable.init(sortableInstance, { cloneSelector: '.sortable-drag', externalDrop: true, onExternalDrop })`. Supply the event definition via each item's `data-drag-data` attribute. The `onExternalDrop` callback receives `args.dragData`, `args.position`, and `args.container` for re-inserting the item when dropped back from the calendar.
- **Dragula list**: initialize `dragula([container])`, then call `dragulaDraggable.init(drake, { externalDrop: true, onExternalDrop })`. The `onExternalDrop` callback works identically to the SortableJS version.
- Handle `onEventCreated` (Vue: `@event-created`): when `args.action === 'externalDrop'`, remove the item from its source list and show a `Toast` with `args.event.title + ' added'`. Handle `onEventDeleted` (Vue: `@event-deleted`) to show a `Toast` with `args.event.title + ' unscheduled'`.
- Load initial calendar events from a remote endpoint using `getJson` and assign them to `data`; for the imperative API, call `inst.setEvents(events)` in the callback.

## Run it in your project

1. If not signed up yet, or don't own a license, you can [start a free trial](https://mobiscroll.com/starttrial).

2. Clone the repository:

   ```
   git clone https://github.com/acidb/mobiscroll-demos-react.git
   ```

3. If not installed yet, install the Mobiscroll CLI from NPM:

   ```
   npm install -g @mobiscroll/cli
   ```

4. Config Mobiscroll - open a terminal window in the root directory and run:

   ```
   mobiscroll config react
   ```

5. Install dependencies - open a terminal window in the root directory and run:

   ```
   npm install
   ```

6. Run the project:

   ```
   npm run dev
   ```

On a trial, add `--trial` to the config command: `mobiscroll config react --trial`.

Running a demo locally requires a Mobiscroll trial or a license; setup is per framework at https://mobiscroll.com/docs/react/getting-started/installation

## Related demos

- Previous: [Drag-and-Drop Task Presets](https://demo.mobiscroll.com/react/eventcalendar/external-event-presets.md) - Drag preset task templates from an external list onto a React event calendar — a popup form captures task details and technician at drop time.
