External Resource Drag & Drop

By combining the resourceReorder feature with external resource drag-and-drop, you can easily organize, group, and adjust resources by dragging them into or out of the timeline view.

Dragging resources onto the timeline

Resources can be created by dragging and dropping an external element onto the timeline. In order for that to work you will need to have two things set up:

  • Enable the timeline to receive external resources by setting externalResourceDrop to true.
  • Initialize the external 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.

Use the resource-create for triggering a custom logic on drop like showing a toast.

Dragging resources off the timeline)

Additionally resource can be removed 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 out by setting externalResourceDrag to true.
  • Initialize the

dropcontainer

Resources can also be dropped on another timeline with externalResourceDrop enabled.

When a resource is dropped into an external drop container or another timeline, it will be deleted from the original calendar and the resource-delete will be fired.

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

Looking for external event drag & drop?  Check out this example →
Get started with this example

Demo

Loading the live demo

Preview in full screen

Drag external resources onto a Timeline to add team members, or drag them off to remove them — build crew scheduling and team management.

Code

<script setup>
import {
  MbscButton,
  MbscDraggable,
  MbscDropcontainer,
  MbscEventcalendar,
  MbscPage,
  MbscToast,
  setOptions 
} from '@mobiscroll/vue'
import { nextTick, ref } from 'vue'

setOptions({
  locale:  ,
  theme: ' ',
  themeVariant: ' '
})

const dragElements = ref([])
const dropCont = ref(null)
const toastMessage = ref('')
const isToastOpen = ref(false)
const timelineRef = ref()

const installers = ref([
  {
    id: 'it-1',
    name: 'Installer team 1',
    eventCreation: false,
    reorder: false,
    children: [
      {
        id: 1,
        name: 'Emily Carter',
        color: '#007acc',
        title: 'Electrician'
      },
      {
        id: 2,
        name: 'Michael Lawson',
        color: '#008000',
        title: 'Plumber'
      }
    ]
  },
  // Showing partial data. Download full source.
])

const availableInstallers = ref([
  {
    id: 9,
    name: 'Adam Miller',
    color: '#C70039',
    title: 'Mason'
  },
  {
    id: 10,
    name: 'Isabella Martinez',
    color: '#2ECC71',
    title: 'Surveyor'
  },
  {
    id: 11,
    name: 'Mark White',
    color: '#34495E',
    title: 'Glazier'
  },
  // Showing partial data. Download full source.
])

const tasks = ref([
  {
    id: 1,
    start: '2026-11-01T07:00',
    end: '2026-11-01T13:00',
    title: 'Electrical wiring installation',
    resource: 1
  },
  {
    id: 2,
    start: '2026-11-01T14:00',
    end: '2026-11-01T18:00',
    title: 'Power panel connection',
    resource: 1
  },
  {
    id: 3,
    start: '2026-11-01T08:00',
    end: '2026-11-01T12:00',
    title: 'Water pipe fitting',
    resource: 2
  },
  // Showing partial data. Download full source.
])

const myView = {
  timeline: { type: 'day', resourceReorder: true, startTime: '07:00', endTime: '18:00' }
}

function handleResourceCreate(args) {
  availableInstallers.value = availableInstallers.value.filter(
    (installer) => installer.id !== args.resource.id
  )
  toastMessage.value = args.resource.name + ' added to ' + args.parent.name
  isToastOpen.value = true
}

function handleResourceDelete(args) {
  toastMessage.value = args.resource.name + ' removed from ' + args.parent.name
  isToastOpen.value = true
}

function handleResourceOrderUpdate(args) {
  const parent = args.parent
  const oldParent = args.oldParent

  if (parent && oldParent) {
    toastMessage.value = args.resource.name + ' moved to ' + args.parent.name
    isToastOpen.value = true
  }

  if (parent && parent.children) {
    // Remove placeholder resource
    parent.children = parent.children.filter((child) => !child.placeholder)
  }

  if (oldParent && !oldParent.children.length) {
    // Add placeholder resource
    oldParent.children.push({
      id: 'ph-' + oldParent.id,
      name: 'Drag Technicians here',
      placeholder: true,
      reorder: false
    })
  }
}

function handleItemDrop(args) {
  if (args.data && args.dataType === 'resource') {
    availableInstallers.value = [...availableInstallers.value, args.data]

    nextTick(() => {
      const dragElms = dragElements.value
      if (dragElms && dragElms[dragElms.length - 1]) {
        dragElms[dragElms.length - 1].scrollIntoView({
          behavior: 'smooth',
          block: 'nearest'
        })
      }
    })
  }
}

function handleToastClose() {
  isToastOpen.value = false
}

function addNewTeam() {
  const teamLength = installers.value.length + 1
  const resourceId = 'it-' + teamLength

  installers.value = [
    ...installers.value,
    {
      id: resourceId,
      eventCreation: false,
      reorder: false,
      name: 'Installer team ' + teamLength,
      children: [
        {
          id: 'ph-' + resourceId,
          name: 'Drag Technicians here',
          placeholder: true,
          reorder: false
        }
      ]
    }
  ]

  timelineRef.value.instance.navigateToEvent({
    start: new Date(),
    resource: resourceId
  })
}
</script>

<template>
  <MbscPage cssClass="mds-ext-res-drop">
    <div class="mbsc-grid mbsc-no-padding">
      <div class="mbsc-row">
        <div ref="dropCont" class="mbsc-col-sm-3 mbsc-flex-col mds-ext-res-drop-cont">
          <MbscDropcontainer :element="dropCont" @item-drop="handleItemDrop">
            <div class="mds-ext-res-header">Available technicians</div>
            <div class="mds-ext-res-list">
              <template v-for="(resource, i) in availableInstallers" :key="resource.id">
                <div ref="dragElements" class="mds-ext-res-item mbsc-font">
                  <div class="mbsc-flex">
                    <div class="mds-ext-res-avatar" :style="{ background: resource.color }">
                      {{ resource.name[0] }}
                    </div>
                    <div class="mds-ext-res-cont">
                      <div class="mds-ext-res-name">{{ resource.name }}</div>
                      <div class="mds-ext-res-title">{{ resource.title }}</div>
                    </div>
                  </div>
                  <MbscDraggable :element="dragElements[i]" :dragData="resource" type="resource" />
                </div>
              </template>
            </div>
          </MbscDropcontainer>
        </div>
        <div class="mbsc-col-sm-9 mds-ext-res-drop-calendar">
          <MbscEventcalendar
            ref="timelineRef"
            :data="tasks"
            :dragBetweenResources="false"
            :externalResourceDrop="true"
            :externalResourceDrag="true"
            :resources="installers"
            :view="myView"
            @resource-create="handleResourceCreate"
            @resource-delete="handleResourceDelete"
            @resource-order-update="handleResourceOrderUpdate"
          >
            <template #resourceHeader>
              <div class="mbsc-flex mbsc-align-items-center">
                <div class="mds-ext-res-header mbsc-flex-1-1">Set up teams</div>
                <MbscButton variant="outline" cssClass="mds-ext-res-add" :onClick="addNewTeam">
                  Add team
                </MbscButton>
              </div>
            </template>

            <template #resource="res">
              <div
                v-if="res.isParent || res.placeholder"
                :class="{ 'mds-ext-res-name': true, 'mds-ext-res-name-ph': res.placeholder }"
              >
                {{ res.name }}
              </div>
              <div v-else class="mbsc-flex">
                <div class="mds-ext-res-avatar" :style="{ background: res.color }">
                  {{ res.name[0] }}
                </div>
                <div class="mds-ext-res-cont">
                  <div class="mds-ext-res-name">{{ res.name }}</div>
                  <div class="mds-ext-res-title">{{ res.title }}</div>
                </div>
              </div>
            </template>
          </MbscEventcalendar>
        </div>
      </div>
    </div>
    <MbscToast :message="toastMessage" :isOpen="isToastOpen" @close="handleToastClose" />
  </MbscPage>
</template>

<style>
.mds-ext-res-drop-calendar {
  border-left: 1px solid #ccc;
}

.mds-ext-res-drop-calendar .mbsc-timeline-row {
  height: 50px;
}

.mds-ext-res-drop-calendar .mbsc-timeline-row-gutter {
  height: 8px;
}

.mds-ext-res-drop-calendar .mbsc-timeline-resource-col {
  width: 250px;
}

.mds-ext-res-drop-calendar .mbsc-timeline-parent {
  height: 32px;
}

.mds-ext-res-drop-calendar .mbsc-timeline-resource {
  align-items: center;
  align-content: center;
}

.mds-ext-res-drop-calendar .mds-ext-res-add {
  margin: 0;
}

.mds-ext-res-header {
  font-weight: 600;
  font-size: 0.875em;
  line-height: 38px;
  text-transform: uppercase;
  opacity: 0.6;
}

.mds-ext-res-name-ph {
  font-style: italic;
  opacity: 0.4;
}

.mds-ext-res-drop-cont .mds-ext-res-header {
  padding: 12px 16px;
  line-height: 1.5em;
}

.mds-ext-res-list {
  overflow: auto;
}

.mds-ext-res-item {
  background: rgba(128, 128, 128, 0.6);
  padding: 10px;
  margin: 16px;
  border-radius: 8px;
}

.mds-ext-res-item:first-child {
  margin-top: 0;
}

.mds-ext-res-cont {
  padding: 0 7px;
}

.mds-ext-res-name {
  font-size: 14px;
  font-weight: 600;
}

.mds-ext-res-title {
  padding-top: 2px;
  font-size: 12px;
  font-weight: 400;
  line-height: 16px;
}

.mds-ext-res-avatar {
  width: 36px;
  min-width: 36px;
  height: 36px;
  line-height: 36px;
  border-radius: 18px;
  text-align: center;
  color: #fff;
  font-size: 22px;
  font-weight: 400;
}

.mds-ext-res-drop,
.mds-ext-res-drop .mbsc-grid,
.mds-ext-res-drop .mbsc-row,
.mds-ext-res-drop-cont,
.mds-ext-res-drop-calendar {
  height: 100%;
}
</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.