---
title: "Vue Timeline External Resource Drag & Drop Example | Mobiscroll"
component: "Timeline"
framework: "Vue"
library_version: "6.2.0"
url: "https://demo.mobiscroll.com/vue/timeline/resource-management-with-dynamic-team-creation"
---

# External Resource Drag & Drop - Vue Timeline demo

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

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

## Description

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

You can also use the [`resource-drag-leave`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onResourceDragLeave) and [`resource-drag-enter`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onResourceDragEnter), 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 →](https://demo.mobiscroll.com/vue/timeline/external-drag-drop-sortable-dragula)

## What this demo shows

- A desktop day timeline for dynamic resource management, with the selected day arranged horizontally and a hierarchical installer team resource tree arranged vertically on the left.
- **External technician list** An external list beside the timeline shows available technicians that can be dragged into installer teams.
- **Header navigation** The month and year label opens date navigation, while the previous and next arrows and the Today button move between days or return to the current day.
- **Day strip** The fixed strip below the header shows the selected day using the `DD DDD MMM YYYY` date format, with the current date highlighted.
- **Time grid** The timeline shows working hours from 7 AM to 5 PM with hourly columns.
- **Resources** The resource column shows expandable and collapsible installer teams with technician child resources.
- **Resource rendering** Technician rows display avatar initials, the technician name, and the job title or specialization.
- **Adding teams** The resource header includes an Add team action for creating new empty installer teams.
- **Empty teams** Empty teams display a "Drag Technicians here" placeholder to indicate valid drop targets.
- **Team assignment** Technicians can be dragged from the external list into installer teams.
- **Team reorganization** Technicians can be dragged between teams to update team composition.
- **Drag-and-drop feedback** Drop targets are highlighted while a technician is being dragged.
- **Confirmation feedback** A toast notification confirms when a technician is assigned to a team.
- **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.
- **Event cards** Events appear as colored cards with a left color stripe, job name, and exact start and end time.
- **Event positioning** Events are placed by assigned resource, date, start time, and end time.
- **Event interaction** Events highlight on hover and expose drag and resize handles for moving events or changing their duration.
- **Event selection** Clicking an event selects and highlights it.
- **Scrolling** Horizontal and vertical scrolling support larger technician lists, multiple installer teams, and extended schedules.

## Best for

- **Construction workforce planning** Organizing installers into teams and assigning scheduled work from the same timeline interface.
- **Field service dispatching** Managing technician availability, team assignments, and time-based jobs in a resource timeline.
- **Installer team management** Creating empty teams, filling them by drag and drop, and reorganizing team composition as plans change.
- **Crew scheduling** Coordinating multiple teams and technicians across a single workday with visible job timing.
- **Operational scheduling tools** Building scheduling interfaces where resource grouping, external resource drag and drop, and timeline-based job placement need to work together.

## 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
- [`navigateToEvent`](https://mobiscroll.com/docs/vue/eventcalendar/api#method-navigateToEvent) - method
- [`data`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-data) - option
- [`dragBetweenResources`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-dragBetweenResources) - option
- [`externalResourceDrop`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-externalResourceDrop) - option
- [`externalResourceDrag`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-externalResourceDrag) - option
- [`resources`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-resources) - option
- [`view`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-view) - option
- [`resource-create`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onResourceCreate) - event
- [`resource-delete`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onResourceDelete) - event
- [`resource-order-update`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onResourceOrderUpdate) - event
- [`cssClass`](https://mobiscroll.com/docs/vue/forms/button#opt-cssClass) - option
- [`close`](https://mobiscroll.com/docs/vue/notifications/toast#event-onClose) - event

## Code

### SFC JS

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

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

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-02T07:00',
    end: '2026-11-02T13:00',
    title: 'Electrical wiring installation',
    resource: 1
  },
  {
    id: 2,
    start: '2026-11-02T14:00',
    end: '2026-11-02T18:00',
    title: 'Power panel connection',
    resource: 1
  },
  {
    id: 3,
    start: '2026-11-02T08:00',
    end: '2026-11-02T12: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>
```

### SFC TS

```vue
<script setup lang="ts">
import {
  MbscButton,
  MbscDraggable,
  MbscDropcontainer,
  MbscEventcalendar,
  MbscPage,
  MbscToast,
  setOptions
} from '@mobiscroll/vue'
import type {
  MbscCalendarEvent,
  MbscEventcalendarView,
  MbscItemDragEvent,
  MbscResource,
  MbscResourceCreateEvent,
  MbscResourceDeleteEvent,
  MbscResourceOrderEvent
} from '@mobiscroll/vue'
import { nextTick, ref } from 'vue'

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

const dragElements = ref<HTMLDivElement[]>([])
const dropCont = ref<HTMLDivElement | null>(null)
const toastMessage = ref('')
const isToastOpen = ref(false)
const timelineRef = ref<typeof MbscEventcalendar>()

const installers = ref<MbscResource[]>([
  {
    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<MbscResource[]>([
  {
    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<MbscCalendarEvent[]>([
  {
    id: 1,
    start: '2026-11-02T07:00',
    end: '2026-11-02T13:00',
    title: 'Electrical wiring installation',
    resource: 1
  },
  {
    id: 2,
    start: '2026-11-02T14:00',
    end: '2026-11-02T18:00',
    title: 'Power panel connection',
    resource: 1
  },
  {
    id: 3,
    start: '2026-11-02T08:00',
    end: '2026-11-02T12:00',
    title: 'Water pipe fitting',
    resource: 2
  },
  // Showing partial data. Download full source.
])

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

function handleResourceCreate(args: MbscResourceCreateEvent) {
  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: MbscResourceDeleteEvent) {
  toastMessage.value = args.resource.name + ' removed from ' + args.parent?.name
  isToastOpen.value = true
}

function handleResourceOrderUpdate(args: MbscResourceOrderEvent) {
  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 && !oldParent.children.length) {
    // Add placeholder resource
    oldParent.children.push({
      id: 'ph-' + oldParent.id,
      name: 'Drag Technicians here',
      placeholder: true,
      reorder: false
    })
  }
}

function handleItemDrop(args: MbscItemDragEvent<MbscResource>) {
  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>
```

## Implementation notes

- Use `type: 'day'` with `startTime: '07:00'`/`endTime: '18:00'` and `resourceReorder: true` in the view config to allow drag-reordering of resources within the timeline.
- Set `externalResourceDrop: true` to allow resources dragged from the sidebar to be added to the timeline, and `externalResourceDrag: true` to allow timeline resources to be dragged back out. Set `dragBetweenResources: false` to prevent events from being reassigned across team members.
- Define the timeline resources as a two-level tree of team groups (`eventCreation: false`, `reorder: false`) with installer leaf nodes (custom `name`, `color`, `title`). When a team has no members, give it a single placeholder child (`placeholder: true`, `reorder: false`) with a descriptive name like "Drag Technicians here".
- Build a separate `availableInstallers` state for the sidebar list. Render each available installer as an `Installer` card component that uses a ref callback to obtain its DOM element, then mounts a `Draggable` with `dragData` set to the resource object and `type: 'resource'`.
- Wrap the sidebar list in a `Dropcontainer` (with its DOM element passed via a ref callback to the `element` prop). In `onItemDrop`, check that `args.dataType === 'resource'` and add `args.data` back to `availableInstallers`. After adding, call `scrollIntoView` on the last card's ref to bring the returned installer into view.
- In `onResourceCreate`, remove the dropped installer from `availableInstallers` by ID, and show a `Toast` announcing that the person was added to `args.parent.name`.
- In `onResourceDelete`, show a `Toast` announcing the removal from `args.parent.name`.
- In `onResourceOrderUpdate`, show a `Toast` when `args.parent` and `args.oldParent` both exist (indicating a cross-team move). Remove the placeholder child from the destination team and, if the source team is now empty, push a new placeholder child onto its `children` array.
- Use `renderResourceHeader` (Angular: `resourceHeaderTemplate`, Vue: `#resourceHeader` slot) to render a "Set up teams" label alongside an "Add team" `Button`.
- Use `renderResource` (Angular: `resourceTemplate`, Vue: `#resource` slot) to handle three cases: parent/group nodes render just the team name; placeholder nodes render a styled placeholder label; leaf nodes render a colored avatar (first letter of the name), the installer's name, and their job title.
- In the Add team handler, append a new group to the installers array with a placeholder child, then call `navigateToEvent` on the calendar instance ref with the new resource ID to scroll the timeline to the newly added team.

## 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: [Resource Search and Filtering](https://demo.mobiscroll.com/vue/timeline/resource-filtering-search.md) - Add live search and filter to a Vue Timeline resource header — multi-select by status or group, with a custom empty state and reset.
- Next: [Event Grouping & Bulk Actions](https://demo.mobiscroll.com/vue/timeline/event-grouping-bulk-action.md) - Group Vue Timeline events into collapsible client/quarter blocks and bulk-drag groups — switch between assignee and work-type views on a full-year layout.
