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

# External Resource Drag & Drop - JavaScript Timeline demo

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

- [Live demo](https://demo.mobiscroll.com/javascript/timeline/resource-management-with-dynamic-team-creation)
- [View code on GitHub](https://github.com/acidb/mobiscroll-demos-javascript/tree/main/src/demos/eventcalendar/timeline/resource-management-with-dynamic-team-creation)
- This demo in other frameworks: [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), [Vue](https://demo.mobiscroll.com/vue/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/javascript/eventcalendar/api#opt-externalResourceDrop) to `true`.
- Initialize the external resources (containers) as `draggable` components.

Set up a container as draggable with the `mbsc-draggable` attribute. Pass a skeleton event definition through the `data-drag-data` attribute as a stringified json object. This will be added to the event calendar on drop. If omitted, a default event will be created. For a more programmatic approach you might want to use the component initialization where the `draggable` is initialized in js code and the `dragData` can be passed as an object.

Use the [`onResourceCreate`](https://mobiscroll.com/docs/javascript/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/javascript/eventcalendar/api#opt-externalResourceDrag) to `true`.
- Initialize the

[`dropcontainer`](https://mobiscroll.com/docs/javascript/eventcalendar/drag-and-drop#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 [`onResourceDelete`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onResourceDelete) will be fired.

You can also use the [`onResourceDragLeave`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onResourceDragLeave) and [`onResourceDragEnter`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onResourceDragEnter), and the drop container's `onItemDragEnter` and `onItemDragLeave` 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/javascript/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/javascript/eventcalendar/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-themeVariant) - option
- [`data`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-data) - option
- [`dragBetweenResources`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-dragBetweenResources) - option
- [`externalResourceDrop`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-externalResourceDrop) - option
- [`externalResourceDrag`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-externalResourceDrag) - option
- [`resources`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-resources) - option
- [`view`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-view) - option
- [`renderResourceHeader`](https://mobiscroll.com/docs/javascript/eventcalendar/api#renderer-renderResourceHeader) - renderer
- [`renderResource`](https://mobiscroll.com/docs/javascript/eventcalendar/api#renderer-renderResource) - renderer
- [`onResourceCreate`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onResourceCreate) - event
- [`onResourceDelete`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onResourceDelete) - event
- [`onResourceOrderUpdate`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onResourceOrderUpdate) - event
- [`setOptions`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-setOptions) - method
- [`navigateToEvent`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-navigateToEvent) - method

## Code

### JS

```js
mobiscroll.setOptions({
  theme: 'ios',
  themeVariant: 'light'
});

var availableWorkersList = document.getElementById('demo-ext-res-drop-list');

var installers = [
  {
    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.
];

var availableInstallers = [
  {
    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.
];

var tasks = [
  {
    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.
];

function generateExternalResourceItem(installer) {
  var installerElm = document.createElement('div');

  installerElm.innerHTML =
    '<div id="demo-ext-res-' +
    installer.id +
    '" class="mds-ext-res-item mbsc-font">' +
    '<div class="mbsc-flex">' +
    '<div class="mds-ext-res-avatar" style="background: ' +
    installer.color +
    '">' +
    installer.name[0] +
    '</div>' +
    '<div class="mds-ext-res-cont">' +
    '<div class="mds-ext-res-name">' +
    installer.name +
    '</div>' +
    '<div class="mds-ext-res-title">' +
    installer.title +
    '</div>' +
    '</div>' +
    '</div>';

  availableWorkersList.append(installerElm);

  mobiscroll.draggable('#demo-ext-res-' + installer.id, {
    dragData: installer,
    type: 'resource',
  });

  return installerElm;
}

var timelineInst = mobiscroll.eventcalendar('#demo-ext-res-drop-calendar', {
  data: tasks,
  dragBetweenResources: false,
  externalResourceDrop: true,
  externalResourceDrag: true,
  resources: installers,
  view: {
    timeline: { type: 'day', resourceReorder: true, startTime: '07:00', endTime: '18:00', listing: true },
  },
  renderResourceHeader: function () {
    return (
      '<div class="mbsc-flex mbsc-align-items-center"><div class="mds-ext-res-header mbsc-flex-1-1">Set up teams</div>' +
      '<button mbsc-button id="demo-ext-res-add" data-variant="outline" class="mds-ext-res-add">Add team</button></div>'
    );
  },
  renderResource: function (resource) {
    return resource.isParent || resource.placeholder
      ? '<div class="mds-ext-res-name' + (resource.placeholder ? ' mds-ext-res-name-ph' : '') + '">' + resource.name + '</div>'
      : '<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>';
  },
  onResourceCreate: function (args) {
    var newResourceId = args.resource.id;
    availableInstallers = availableInstallers.filter(function (installer) {
      return installer.id !== newResourceId;
    });

    document.getElementById('demo-ext-res-' + newResourceId).remove();

    mobiscroll.toast({
      message: args.resource.name + ' added to ' + args.parent.name,
    });
  },
  onResourceDelete: function (args) {
    mobiscroll.toast({
      message: args.resource.name + ' removed from ' + args.parent.name,
    });
  },
  onResourceOrderUpdate: function (args) {
    installers = args.resources;
    var parent = args.parent;
    var oldParent = args.oldParent;

    if (parent && parent.children) {
      // Remove placeholder resource
      parent.children = parent.children.filter(function (child) {
        return !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,
      });
    }
  },
});

mobiscroll.dropcontainer('#demo-ext-res-drop-cont', {
  onItemDrop: function (args) {
    var installer = args.data;
    if (installer && args.dataType === 'resource') {
      availableInstallers.push(installer);
      var item = generateExternalResourceItem(installer);
      item.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
    }
  },
});

document.getElementById('demo-ext-res-drop-calendar').addEventListener('click', function (event) {
  if (event.target.closest('#demo-ext-res-add')) {
    var teamLength = installers.length + 1;
    var resourceId = 'it-' + teamLength;
    installers.push({
      id: resourceId,
      eventCreation: false,
      reorder: false,
      name: 'Installer team ' + teamLength,
      children: [
        {
          id: 'ph-' + resourceId,
          name: 'Drag Technicians here',
          placeholder: true,
          reorder: false,
        },
      ],
    });

    timelineInst.setOptions({ resources: installers.slice() });

    setTimeout(function () {
      timelineInst.navigateToEvent({
        start: new Date(),
        resource: resourceId,
      });
    });
  }
});

availableInstallers.forEach(generateExternalResourceItem);
```

### HTML

```html
<div class="mds-ext-res-drop" mbsc-page>
  <div class="mbsc-grid mbsc-no-padding">
    <div class="mbsc-row">
      <div id="demo-ext-res-drop-cont" class="mbsc-col-sm-3 mbsc-flex-col mds-ext-res-drop-cont">
        <div class="mds-ext-res-header">Available technicians</div>
        <div id="demo-ext-res-drop-list" class="mds-ext-res-list"></div>
      </div>
      <div class="mbsc-col-sm-9 mds-ext-res-drop-calendar">
        <div id="demo-ext-res-drop-calendar"></div>
      </div>
    </div>
  </div>
</div>
```

### CSS

```css
.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: .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, .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%;
}
```

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

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

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

## Related demos

- Previous: [Resource Search and Filtering](https://demo.mobiscroll.com/javascript/timeline/resource-filtering-search.md) - Add live search and filter to a Javascript 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/javascript/timeline/event-grouping-bulk-action.md) - Group Javascript Timeline events into collapsible client/quarter blocks and bulk-drag groups — switch between assignee and work-type views on a full-year layout.
