Schedule & Unschedule from External Lists

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 and/or 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 and 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 or/and externalResourceDropto 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, event-created or event-create-failed 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:

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 will be fired.

You can also use the event-drag-leave and event-drag-enter, 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.

Unscheduling events or resources to SortableJS and Dragula lists

Dropping events or resources from the Eventcalendar to SortableJS and 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 section in the documentation

Get started with this example

Demo

Loading the live demo

Preview in full screen

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

Code

<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({
  locale:  ,
  theme: ' ',
  themeVariant: ' '
})

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>

Duplicate Session

You have signed in on a different machine. To keep everything in sync, you have been signed out of this session.

If you need to invite team members, manage seats and accounts you can do it from the licenses page.

Duplicate Session

We noticed you are logged in on a different machine and have closed your old session.

If you need to invite team members, manage seats and accounts you can do it from the licenses page.