---
title: "Vue Timeline Schedule & Unschedule from External Lists Example | Mobiscroll"
component: "Timeline"
framework: "Vue"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/vue/timeline/external-drag-drop-sortable-dragula"
---

# Schedule & Unschedule from External Lists - Vue Timeline demo

Connect SortableJS or Dragula lists to the Vue Timeline — drag items onto the timeline to schedule them, drag them back off to unschedule. For Vue and Nuxt.

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

## Description

**Dragging events and resources on the Mobiscroll event calendar**

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

- Enable the timeline to receive external events and/or resources by setting [`externalDrop`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-externalDrop) and/or [`externalResourceDrop`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-externalResourceDrop) to `true`.
- Initialize the external events or/and resources (containers) as `draggable` components.

Use the `MbscDraggable` 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 timeline. This integration enables a smooth workflow where items can be dragged from external lists and scheduled directly onto the calendar.

Events and resources 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 timeline to receive external drops by setting [`externalDrop`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-externalDrop) or/and [`externalResourceDrop`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-externalResourceDrop)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 [`event-create`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventCreate), [`event-created`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventCreated) or [`event-create-failed`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventCreateFailed) to run custom logic (e.g. showing a toast) when a drop succeeds or fails.

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

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

- Enable the timeline to allow dragging events or resources out by setting [`externalDrag`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-externalDrag) or [`externalResourceDrag`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-externalResourceDrag) to `true`.
- Initialize the

`dropcontainer`

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 [`event-delete`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventDelete) will be fired.

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

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

**Unscheduling events or resources to SortableJS and Dragula lists**

Dropping events or resources 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/vue/eventcalendar/drag-and-drop#third-party-dragging-support) section in the documentation

## What this demo shows

- A desktop daily timeline where hours are arranged horizontally and resources are listed as rows on the left.
- **Header navigation** The month and year label opens date navigation, while the previous and next arrows move between weeks and the Today button returns to the current date.
- **Day view** The strip below the header shows the selected day, with the current date highlighted. The timeline displays hourly columns from 12 AM to 12 PM.
- **Resources** Multiple resources are shown as separate timeline rows.
- **Current time** A vertical blue line with a time label marks the current time.
- **Hover behavior** Hovering over the time grid shows a time indicator that follows the cursor in 15-minute increments.
- **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 yellow items, and `Dragula list (externally sortable)` with four green items. Each list contains four grey resources as well.
- **Scheduling from lists** Any item from the external lists can be dragged onto the timeline to create and schedule that event on the target time or drop a resource to the resource column.
- **Unscheduling back to lists** Scheduled events can be dragged out of the timeline and dropped back into an external list, removing them from the day view.
- **List behavior** When an item is scheduled from an external list, it disappears from that list and appears on the timeline.
- **Feedback on drop** After an event is scheduled from an external list, a toast appears near the bottom center of the timeline showing the event title and the `added` meesage.
- **Feedback on externap list drop** After an event is unscheduled from the timeline and dropped on the external list a toast appears near the bottom center of the timeline showing the event title and the `unscheduled` message.
- **Event cards** Events are shown as colored cards with a left color stripe, a bold title, and the start and end time below the title.
- **Date positioning** Events are positioned by their assigned resource and exact date range.
- **Event interaction** Events highlight on hover and show drag and resize handles for repositioning or changing duration.
- **Event selection** Clicking an event selects and highlights it.
- **Event creation** New events can be created by double-clicking the timeline or by clicking and dragging across a time range.
- **Scrolling** The timeline supports horizontal and vertical scrolling for navigating through time and resources.

## Best for

- **Unscheduled work queues** Managing tasks, appointments, or resources that start in an external backlog and need to be placed onto specific times.
- **Desktop planning workflows** Daily timeline experiences where users need a time-grid view together with a side panel of draggable events and resources.
- **Schedule and unschedule flows** Use cases where users assign items to the timeline, then move them back out to return them to an unscheduled state.
- **Third-party drag-and-drop integration** Projects that combine Mobiscroll timeline with existing `SortableJS` or `Dragula` list implementations.

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-themeVariant) - option
- [`externalDrop`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-externalDrop) - option
- [`view`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-view) - option
- [`dragToMove`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-dragToMove) - option
- [`dragToCreate`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-dragToCreate) - option
- [`externalDrag`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-externalDrag) - option
- [`externalResourceDrop`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-externalResourceDrop) - option
- [`resources`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-resources) - option
- [`event-created`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventCreated) - event
- [`event-deleted`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventDeleted) - event
- [`close`](https://mobiscroll.com/docs/vue/notifications/toast#event-onClose) - event

## Code

### SFC JS

```vue
<script setup>
import {
  dragulaDraggable,
  MbscDraggable,
  MbscDropcontainer,
  MbscEventcalendar,
  MbscToast,
  setOptions,
  sortableJsDraggable
} from '@mobiscroll/vue'
import dragula from 'dragula'
import Sortable from 'sortablejs'
import { onMounted, onUnmounted, ref } from 'vue'
import 'dragula/dist/dragula.css'

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

const dropCont = ref()
const dragTaskElements = ref([])
const dragResourceElements = ref([])
const sortableTaskCont = ref([])
const sortableTaskInstance = ref()
const dragulaTaskCont = ref([])
const drake1 = ref()
const sortableResourceCont = ref([])
const sortableResourceInstance = ref()
const dragulaResourceCont = ref()
const drake2 = ref()
const toastMessage = ref(null)
const isToastOpen = ref(false)

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

const mySortableTasks = ref([
  {
    id: 'sortable-1',
    title: 'Task 5',
    color: '#d1891f',
    start: '2026-10-25T08:00',
    end: '2026-10-25T09:30'
  },
  {
    id: 'sortable-2',
    title: 'Task 6',
    color: '#d1891f',
    start: '2026-10-25T12:00',
    end: '2026-10-25T15:00'
  },
  {
    id: 'sortable-3',
    title: 'Task 7',
    color: '#d1891f',
    start: '2026-10-25T08:30',
    end: '2026-10-25T11:00'
  },
  {
    id: 'sortable-4',
    title: 'Task 8',
    color: '#d1891f',
    start: '2026-10-25T16:00',
    end: '2026-10-25T21:00'
  }
])

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

const myDraggableResources = ref([
  {
    id: 'draggable-1',
    name: 'Resource 1'
  },
  {
    id: 'draggable-2',
    name: 'Resource 2'
  },
  {
    id: 'draggable-3',
    name: 'Resource 3'
  },
  {
    id: 'draggable-4',
    name: 'Resource 4'
  }
])

const mySortableResources = ref([
  {
    id: 'sortable-1',
    name: 'Resource 5'
  },
  {
    id: 'sortable-2',
    name: 'Resource 6'
  },
  {
    id: 'sortable-3',
    name: 'Resource 7'
  },
  {
    id: 'sortable-4',
    name: 'Resource 8'
  }
])

const myDragulaResources = ref([
  {
    id: 'dragula-1',
    name: 'Resource 9'
  },
  {
    id: 'dragula-2',
    name: 'Resource 10'
  },
  {
    id: 'dragula-3',
    name: 'Resource 11'
  },
  {
    id: 'dragula-4',
    name: 'Resource 12'
  }
])

const myResources = [
  { name: 'Resource A', id: 'res-1' },
  { name: 'Resource B', id: 'res-2' },
  { name: 'Resource C', id: 'res-3' },
  { name: 'Resource D', id: 'res-4' },
  { name: 'Resource E', id: 'res-5' }
]

const myView = {
  timeline: { type: 'day' }
}

function handleEventCreated(args) {
  if (args.action === 'externalDrop') {
    myDraggableTasks.value = myDraggableTasks.value.filter((item) => item.id !== args.event.id)
    mySortableTasks.value = mySortableTasks.value.filter((item) => item.id !== args.event.id)
    myDragulaTasks.value = myDragulaTasks.value.filter((item) => item.id !== args.event.id)
    toastMessage.value = args.event.title + ' added'
    isToastOpen.value = true
  }
}

function handleEventDeleted(args) {
  toastMessage.value = args.event.title + ' unscheduled'
  isToastOpen.value = true
}

function handleResourceCreated(args) {
  if (args.type === 'onResourceCreated') {
    myDraggableResources.value = myDraggableResources.value.filter(
      (item) => item.id !== args.resource.id
    )
    mySortableResources.value = mySortableResources.value.filter(
      (item) => item.id !== args.resource.id
    )
    myDragulaResources.value = myDragulaResources.value.filter(
      (item) => item.id !== args.resource.id
    )
    toastMessage.value = args.resource.name + ' added'
    isToastOpen.value = true
  }
}

function handleItemDrop(args) {
  if (args.data) {
    myDraggableTasks.value = [...myDraggableTasks.value, args.data]
  }
}

function getHours(event) {
  const eventLength = Math.round(
    Math.abs(new Date(event.end) - new Date(event.start)) / (60 * 60 * 1000)
  )
  return eventLength + ' hour' + (eventLength > 1 ? 's' : '')
}

function handleToastClose() {
  isToastOpen.value = false
}

onMounted(() => {
  if (sortableTaskCont.value) {
    sortableTaskInstance.value = new Sortable(sortableTaskCont.value, {
      animation: 150,
      forceFallback: true
    })

    sortableJsDraggable.init(sortableTaskInstance.value, {
      cloneSelector: '.sortable-drag',
      externalDrop: true,
      onExternalDrop: (a) => {
        const dragData = a.dragData
        const newTasks = [...mySortableTasks.value]
        newTasks.splice(a.position, 0, dragData)
        mySortableTasks.value = newTasks
      }
    })
  }

  if (sortableResourceCont.value) {
    sortableResourceInstance.value = new Sortable(sortableResourceCont.value, {
      animation: 150,
      forceFallback: true
    })

    sortableJsDraggable.init(sortableResourceInstance.value, {
      cloneSelector: '.sortable-drag',
      type: 'resource'
    })
  }

  if (dragulaTaskCont.value) {
    drake1.value = dragula([dragulaTaskCont.value])
    dragulaDraggable.init(drake1.value, {
      externalDrop: true,
      onExternalDrop: (a) => {
        const dragData = a.dragData
        const newTasks = [...myDraggableTasks.value]
        newTasks.splice(a.position, 0, dragData)
        myDraggableTasks.value = newTasks
      }
    })
  }

  if (dragulaResourceCont.value) {
    drake2.value = dragula([dragulaResourceCont.value])
    dragulaDraggable.init(drake2.value, {
      type: 'resource'
    })
  }
})

onUnmounted(() => {
  if (sortableTaskInstance.value) {
    sortableTaskInstance.value.destroy()
  }
  if (sortableResourceInstance.value) {
    sortableResourceInstance.value.destroy()
  }
  if (drake1.value) {
    drake1.value.destroy()
  }
  if (drake2.value) {
    drake2.value.destroy()
  }
})
</script>

<template>
  <div class="mbsc-grid mbsc-no-padding mds-drag-drop-sort mds-full-height">
    <div class="mbsc-row mds-full-height">
      <div class="mbsc-col-sm-3 mds-drag-drop-sort-container-wrapper mds-full-height">
        <div class="mbsc-txt-muted mds-third-party-title">Mobiscroll draggable</div>
        <div class="mbsc-flex">
          <div class="mbsc-col-sm-6 mbsc-flex-col">
            <div ref="dropCont" class="mds-drag-drop-sort-container mbsc-flex-col mbsc-flex-1-0">
              <div class="mbsc-txt-muted mds-third-party-list-title">Event list</div>
              <MbscDropcontainer :element="dropCont" @item-drop="handleItemDrop($event)">
                <div v-for="(task, i) in myDraggableTasks" :key="task.id">
                  <div
                    ref="dragTaskElements"
                    class="mds-drag-drop-sort-task"
                    :style="{ background: task.color }"
                  >
                    <div>{{ task.title }}</div>
                    <div>{{ getHours(task) }}</div>
                    <MbscDraggable :element="dragTaskElements[i]" :dragData="task" />
                  </div>
                </div>
              </MbscDropcontainer>
            </div>
          </div>
          <div class="mbsc-col-sm-6 mbsc-flex-col">
            <div class="mds-drag-drop-sort-container mbsc-flex-col mbsc-flex-1-0">
              <div class="mds-third-party-list-title mbsc-txt-muted">Resource list</div>
              <div
                v-for="(resource, i) in myDraggableResources"
                :key="resource.id"
                ref="dragResourceElements"
              >
                <div class="mds-drag-drop-sort-task" :style="{ background: '#80808099' }">
                  <div>{{ resource.name }}</div>
                  <MbscDraggable
                    :element="dragResourceElements[i]"
                    :dragData="resource"
                    type="resource"
                  />
                </div>
              </div>
            </div>
          </div>
        </div>
        <div class="mbsc-txt-muted mds-third-party-title">SortableJS (externally sortable)</div>
        <div class="mbsc-flex">
          <div class="mbsc-col-sm-6 mbsc-flex-col">
            <div class="mds-drag-drop-sort-container mbsc-flex-col mbsc-flex-1-0">
              <div class="mds-third-party-list-title mbsc-txt-muted">Event list</div>
              <div class="mbsc-flex-col mbsc-flex-1-0" ref="sortableTaskCont">
                <div
                  v-for="task in mySortableTasks"
                  :key="task.id"
                  class="mds-drag-drop-sort-task"
                  :style="{ background: task.color }"
                  :data-drag-data="JSON.stringify(task)"
                >
                  <div>{{ task.title }}</div>
                  <div>{{ getHours(task) }}</div>
                </div>
              </div>
            </div>
          </div>
          <div class="mbsc-col-sm-6 mbsc-flex-col">
            <div class="mds-drag-drop-sort-container mbsc-flex-col mbsc-flex-1-0">
              <div class="mds-third-party-list-title mbsc-txt-muted">Resource list</div>
              <div class="mbsc-flex-col mbsc-flex-1-0" ref="sortableResourceCont">
                <div
                  v-for="resource in mySortableResources"
                  :key="resource.id"
                  class="mds-drag-drop-sort-task"
                  :style="{ background: '#80808099' }"
                  :data-drag-data="JSON.stringify(resource)"
                >
                  <div>{{ resource.name }}</div>
                </div>
              </div>
            </div>
          </div>
        </div>
        <div class="mbsc-txt-muted mds-third-party-title">Dragula (externally sortable)</div>
        <div class="mbsc-flex">
          <div class="mbsc-col-sm-6 mbsc-flex-col">
            <div class="mds-drag-drop-sort-container mbsc-flex-col mbsc-flex-1-0">
              <div class="mds-third-party-list-title mbsc-txt-muted">Event list</div>
              <div class="mbsc-flex-col mbsc-flex-1-0" ref="dragulaTaskCont">
                <div
                  v-for="task in myDragulaTasks"
                  :key="task.id"
                  class="mds-drag-drop-sort-task"
                  :style="{ background: task.color }"
                  :data-drag-data="JSON.stringify(task)"
                >
                  <div>{{ task.title }}</div>
                  <div>{{ getHours(task) }}</div>
                </div>
              </div>
            </div>
          </div>
          <div class="mbsc-col-sm-6 mbsc-flex-col">
            <div class="mds-drag-drop-sort-container mbsc-flex-col mbsc-flex-1-0">
              <div class="mds-third-party-list-title mbsc-txt-muted">Resource list</div>
              <div class="mbsc-flex-col mbsc-flex-1-0" ref="dragulaResourceCont">
                <div
                  v-for="resource in myDragulaResources"
                  :key="resource.id"
                  class="mds-drag-drop-sort-task"
                  :style="{ background: '#80808099' }"
                  :data-drag-data="JSON.stringify(resource)"
                >
                  <div>{{ resource.name }}</div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div class="mbsc-col-sm-9 mds-drag-drop-sort-calendar mds-full-height">
        <MbscEventcalendar
          :view="myView"
          :dragToMove="true"
          :dragToCreate="true"
          :externalDrop="true"
          :externalDrag="true"
          :externalResourceDrop="true"
          :resources="myResources"
          @event-created="handleEventCreated"
          @resource-created="handleResourceCreated"
          @event-deleted="handleEventDeleted"
        />
      </div>
    </div>
    <MbscToast :message="toastMessage" :isOpen="isToastOpen" @close="handleToastClose" />
  </div>
</template>

<style>
.mds-full-height {
  height: 100%;
}

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

.mds-drag-drop-sort .mbsc-timeline-resource-col {
  width: 100px;
}

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

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

.mds-drag-drop-sort-task {
  margin-top: 5px;
  padding: 10px 12px 10px 12px;
  align-content: center;
  touch-action: none;
  color: #fff;
  border-radius: 8px;
  flex: 0 0 4.2em;
  font-family:
    -apple-system,
    Segoe UI,
    Roboto,
    sans-serif;
  font-size: 12px;
}

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

.mds-third-party-list-title {
  font-weight: 600;
  padding: 0 5px;
  font-size: 12px;
  text-transform: none;
}

.mds-drag-drop-sort-duration {
  font-size: 10px;
}
</style>
```

### SFC TS

```vue
<script setup lang="ts">
import {
  dragulaDraggable,
  MbscDraggable,
  MbscDropcontainer,
  MbscEventcalendar,
  MbscToast,
  setOptions,
  sortableJsDraggable
} from '@mobiscroll/vue'
import type {
  MbscCalendarEvent,
  MbscEventcalendarView,
  MbscEventCreatedEvent,
  MbscEventDeletedEvent,
  MbscExternalDropEvent,
  MbscItemDragEvent,
  MbscResource,
  MbscResourceDeletedEvent
} from '@mobiscroll/vue'
import dragula, { type Drake } from 'dragula'
import Sortable from 'sortablejs'
import { onMounted, onUnmounted, ref } from 'vue'
import 'dragula/dist/dragula.css'

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

const dropCont = ref<HTMLDivElement>()
const dragTaskElements = ref([])
const dragResourceElements = ref([])
const sortableTaskCont = ref<HTMLDivElement>()
const sortableTaskInstance = ref<Sortable>()
const dragulaTaskCont = ref<HTMLDivElement>()
const drake1 = ref<Drake>()
const sortableResourceCont = ref<HTMLDivElement>()
const sortableResourceInstance = ref<Sortable>()
const dragulaResourceCont = ref<HTMLDivElement>()
const drake2 = ref<Drake>()
const toastMessage = ref<string>('')
const isToastOpen = ref<boolean>(false)

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

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

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

const myDraggableResources = ref<MbscResource[]>([
  {
    id: 'draggable-1',
    name: 'Resource 1'
  },
  {
    id: 'draggable-2',
    name: 'Resource 2'
  },
  {
    id: 'draggable-3',
    name: 'Resource 3'
  },
  {
    id: 'draggable-4',
    name: 'Resource 4'
  }
])

const mySortableResources = ref<MbscResource[]>([
  {
    id: 'sortable-1',
    name: 'Resource 5'
  },
  {
    id: 'sortable-2',
    name: 'Resource 6'
  },
  {
    id: 'sortable-3',
    name: 'Resource 7'
  },
  {
    id: 'sortable-4',
    name: 'Resource 8'
  }
])

const myDragulaResources = ref<MbscResource[]>([
  {
    id: 'dragula-1',
    name: 'Resource 9'
  },
  {
    id: 'dragula-2',
    name: 'Resource 10'
  },
  {
    id: 'dragula-3',
    name: 'Resource 11'
  },
  {
    id: 'dragula-4',
    name: 'Resource 12'
  }
])

const myResources: MbscResource[] = [
  { name: 'Resource A', id: 'res-1' },
  { name: 'Resource B', id: 'res-2' },
  { name: 'Resource C', id: 'res-3' },
  { name: 'Resource D', id: 'res-4' },
  { name: 'Resource E', id: 'res-5' }
]

const myView: MbscEventcalendarView = {
  timeline: { type: 'day' }
}

function handleEventCreated(args: MbscEventCreatedEvent) {
  if (args.action === 'externalDrop') {
    myDraggableTasks.value = myDraggableTasks.value.filter((item) => item.id !== args.event.id)
    mySortableTasks.value = mySortableTasks.value.filter((item) => item.id !== args.event.id)
    myDragulaTasks.value = myDragulaTasks.value.filter((item) => item.id !== args.event.id)
    toastMessage.value = args.event.title + ' added'
    isToastOpen.value = true
  }
}

function handleEventDeleted(args: MbscEventDeletedEvent) {
  toastMessage.value = args.event.title + ' unscheduled'
  isToastOpen.value = true
}

function handleResourceCreated(args: MbscResourceDeletedEvent) {
  if (args.type === 'onResourceCreated') {
    myDraggableResources.value = myDraggableResources.value.filter(
      (item) => item.id !== args.resource.id
    )
    mySortableResources.value = mySortableResources.value.filter(
      (item) => item.id !== args.resource.id
    )
    myDragulaResources.value = myDragulaResources.value.filter(
      (item) => item.id !== args.resource.id
    )
    toastMessage.value = args.resource.name + ' added'
    isToastOpen.value = true
  }
}

function handleItemDrop(args: MbscItemDragEvent) {
  if (args.data) {
    myDraggableTasks.value = [...myDraggableTasks.value, args.data]
  }
}

function getHours(event: any) {
  const eventLength = Math.round(
    Math.abs(new Date(event.end).getTime() - new Date(event.start).getTime()) / (60 * 60 * 1000)
  )
  return eventLength + ' hour' + (eventLength > 1 ? 's' : '')
}

function handleToastClose() {
  isToastOpen.value = false
}

onMounted(() => {
  if (sortableTaskCont.value) {
    sortableTaskInstance.value = new Sortable(sortableTaskCont.value, {
      animation: 150,
      forceFallback: true
    })

    sortableJsDraggable.init(sortableTaskInstance.value, {
      cloneSelector: '.sortable-drag',
      externalDrop: true,
      onExternalDrop: (a: MbscExternalDropEvent) => {
        const dragData = a.dragData
        const newTasks = [...mySortableTasks.value]
        newTasks.splice(a.position, 0, dragData)
        mySortableTasks.value = newTasks
      }
    })
  }

  if (sortableResourceCont.value) {
    sortableResourceInstance.value = new Sortable(sortableResourceCont.value, {
      animation: 150,
      forceFallback: true
    })

    sortableJsDraggable.init(sortableResourceInstance.value, {
      cloneSelector: '.sortable-drag',
      type: 'resource'
    })
  }

  if (dragulaTaskCont.value) {
    drake1.value = dragula([dragulaTaskCont.value])
    dragulaDraggable.init(drake1.value, {
      externalDrop: true,
      onExternalDrop: (a: MbscExternalDropEvent) => {
        const dragData = a.dragData
        const newTasks = [...myDraggableTasks.value]
        newTasks.splice(a.position, 0, dragData)
        myDraggableTasks.value = newTasks
      }
    })
  }

  if (dragulaResourceCont.value) {
    drake2.value = dragula([dragulaResourceCont.value])
    dragulaDraggable.init(drake2.value, {
      type: 'resource'
    })
  }
})

onUnmounted(() => {
  if (sortableTaskInstance.value) {
    sortableTaskInstance.value.destroy()
  }
  if (sortableResourceInstance.value) {
    sortableResourceInstance.value.destroy()
  }
  if (drake1.value) {
    drake1.value.destroy()
  }
  if (drake2.value) {
    drake2.value.destroy()
  }
})
</script>

<template>
  <div class="mbsc-grid mbsc-no-padding mds-drag-drop-sort mds-full-height">
    <div class="mbsc-row mds-full-height">
      <div class="mbsc-col-sm-3 mds-drag-drop-sort-container-wrapper mds-full-height">
        <div class="mbsc-txt-muted mds-third-party-title">Mobiscroll draggable</div>
        <div class="mbsc-flex">
          <div class="mbsc-col-sm-6 mbsc-flex-col">
            <div ref="dropCont" class="mds-drag-drop-sort-container mbsc-flex-col mbsc-flex-1-0">
              <div class="mbsc-txt-muted mds-third-party-list-title">Event list</div>
              <MbscDropcontainer :element="dropCont" @item-drop="handleItemDrop($event)">
                <div v-for="(task, i) in myDraggableTasks" :key="task.id">
                  <div
                    ref="dragTaskElements"
                    class="mds-drag-drop-sort-task"
                    :style="{ background: task.color }"
                  >
                    <div>{{ task.title }}</div>
                    <div>{{ getHours(task) }}</div>
                    <MbscDraggable :element="dragTaskElements[i]" :dragData="task" />
                  </div>
                </div>
              </MbscDropcontainer>
            </div>
          </div>
          <div class="mbsc-col-sm-6 mbsc-flex-col">
            <div class="mds-drag-drop-sort-container mbsc-flex-col mbsc-flex-1-0">
              <div class="mds-third-party-list-title mbsc-txt-muted">Resource list</div>
              <div
                v-for="(resource, i) in myDraggableResources"
                :key="resource.id"
                ref="dragResourceElements"
              >
                <div class="mds-drag-drop-sort-task" :style="{ background: '#80808099' }">
                  <div>{{ resource.name }}</div>
                  <MbscDraggable
                    :element="dragResourceElements[i]"
                    :dragData="resource"
                    type="resource"
                  />
                </div>
              </div>
            </div>
          </div>
        </div>
        <div class="mbsc-txt-muted mds-third-party-title">SortableJS (externally sortable)</div>
        <div class="mbsc-flex">
          <div class="mbsc-col-sm-6 mbsc-flex-col">
            <div class="mds-drag-drop-sort-container mbsc-flex-col mbsc-flex-1-0">
              <div class="mds-third-party-list-title mbsc-txt-muted">Event list</div>
              <div class="mbsc-flex-col mbsc-flex-1-0" ref="sortableTaskCont">
                <div
                  v-for="task in mySortableTasks"
                  :key="task.id"
                  class="mds-drag-drop-sort-task"
                  :style="{ background: task.color }"
                  :data-drag-data="JSON.stringify(task)"
                >
                  <div>{{ task.title }}</div>
                  <div>{{ getHours(task) }}</div>
                </div>
              </div>
            </div>
          </div>
          <div class="mbsc-col-sm-6 mbsc-flex-col">
            <div class="mds-drag-drop-sort-container mbsc-flex-col mbsc-flex-1-0">
              <div class="mds-third-party-list-title mbsc-txt-muted">Resource list</div>
              <div class="mbsc-flex-col mbsc-flex-1-0" ref="sortableResourceCont">
                <div
                  v-for="resource in mySortableResources"
                  :key="resource.id"
                  class="mds-drag-drop-sort-task"
                  :style="{ background: '#80808099' }"
                  :data-drag-data="JSON.stringify(resource)"
                >
                  <div>{{ resource.name }}</div>
                </div>
              </div>
            </div>
          </div>
        </div>
        <div class="mbsc-txt-muted mds-third-party-title">Dragula (externally sortable)</div>
        <div class="mbsc-flex">
          <div class="mbsc-col-sm-6 mbsc-flex-col">
            <div class="mds-drag-drop-sort-container mbsc-flex-col mbsc-flex-1-0">
              <div class="mds-third-party-list-title mbsc-txt-muted">Event list</div>
              <div class="mbsc-flex-col mbsc-flex-1-0" ref="dragulaTaskCont">
                <div
                  v-for="task in myDragulaTasks"
                  :key="task.id"
                  class="mds-drag-drop-sort-task"
                  :style="{ background: task.color }"
                  :data-drag-data="JSON.stringify(task)"
                >
                  <div>{{ task.title }}</div>
                  <div>{{ getHours(task) }}</div>
                </div>
              </div>
            </div>
          </div>
          <div class="mbsc-col-sm-6 mbsc-flex-col">
            <div class="mds-drag-drop-sort-container mbsc-flex-col mbsc-flex-1-0">
              <div class="mds-third-party-list-title mbsc-txt-muted">Resource list</div>
              <div class="mbsc-flex-col mbsc-flex-1-0" ref="dragulaResourceCont">
                <div
                  v-for="resource in myDragulaResources"
                  :key="resource.id"
                  class="mds-drag-drop-sort-task"
                  :style="{ background: '#80808099' }"
                  :data-drag-data="JSON.stringify(resource)"
                >
                  <div>{{ resource.name }}</div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div class="mbsc-col-sm-9 mds-drag-drop-sort-calendar mds-full-height">
        <MbscEventcalendar
          :view="myView"
          :dragToMove="true"
          :dragToCreate="true"
          :externalDrop="true"
          :externalDrag="true"
          :externalResourceDrop="true"
          :resources="myResources"
          @event-created="handleEventCreated"
          @resource-created="handleResourceCreated"
          @event-deleted="handleEventDeleted"
        />
      </div>
    </div>
    <MbscToast :message="toastMessage" :isOpen="isToastOpen" @close="handleToastClose" />
  </div>
</template>

<style>
.mds-full-height {
  height: 100%;
}

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

.mds-drag-drop-sort .mbsc-timeline-resource-col {
  width: 100px;
}

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

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

.mds-drag-drop-sort-task {
  margin-top: 5px;
  padding: 10px 12px 10px 12px;
  align-content: center;
  touch-action: none;
  color: #fff;
  border-radius: 8px;
  flex: 0 0 4.2em;
  font-family:
    -apple-system,
    Segoe UI,
    Roboto,
    sans-serif;
  font-size: 12px;
}

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

.mds-third-party-list-title {
  font-weight: 600;
  padding: 0 5px;
  font-size: 12px;
  text-transform: none;
}

.mds-drag-drop-sort-duration {
  font-size: 10px;
}
</style>
```

## Implementation notes

- Use `timeline: { type: 'day' }` with 5 fixed calendar resources (A-E). Enable `externalDrop: true`, `externalDrag: true`, and `externalResourceDrop: true` on the `Eventcalendar`.
- The page has three side-by-side external panel sections, each with an **Event list** and a **Resource list**. The three sections demonstrate three integration paths: Mobiscroll `Draggable`, SortableJS, and Dragula.
- **Mobiscroll Draggable (native path):**
  - Each task item renders a `Draggable` component (Angular: `mbsc-draggable` directive, Vue: `MbscDraggable`) with `dragData` set to the full event object and `element` pointing to the item's DOM node.
  - Resource items use the same pattern with `type="resource"` on the `Draggable`.
  - Wrap the event list in a `Dropcontainer` (Vue: `MbscDropcontainer`) with `onItemDrop` to handle events dragged back from the calendar - add the returned `args.data` back to the list.
- **SortableJS (plugin path):**
  - Initialize `new Sortable(container, { animation: 150, forceFallback: true })` on the list container.
  - Call `sortableJsDraggable.init(sortableInstance, { cloneSelector: '.sortable-drag' })` from `@mobiscroll/*` to connect the list to the calendar. Pass `type: 'resource'` for resource lists. Pass `externalDrop: true` and `onExternalDrop` for event lists that should also receive events back from the calendar; the callback receives `{ dragData, position }` - splice `dragData` into the list at `position`.
  - Event data is read from each item element's `data-drag-data` attribute (a stringified JSON of the event object).
- **Dragula (plugin path):**
  - Initialize `dragula([container])` on the list container.
  - Call `dragulaDraggable.init(drake, { ... })` with the same option shape as the SortableJS path.
- **Calendar event handling:**
  - `onEventCreated`: if `args.action === 'externalDrop'`, remove the item from whichever source list contains a matching `id`, then show a `Toast` confirming the event was added.
  - `onEventDelete`: fires when an event is dragged out of the calendar to the `Dropcontainer` - show a toast indicating the event was unscheduled.
  - `onResourceCreated`: if `args.type === 'onResourceCreated'`, remove the matching resource from the source list and show a toast.
- Store each panel's items as separate mutable state arrays so items disappear from the list once dropped onto the calendar and re-appear when dragged back.

## 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-vue.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 vue
   ```

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 vue --trial`.

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

## Related demos

- Previous: [Prevent Event Overlap](https://demo.mobiscroll.com/vue/timeline/prevent-double-booking-events.md) - Prevent double booking on the Vue Timeline — block overlaps per event, per resource, or globally, and show a toast on conflict.
