Drag People onto Events

Assigning people to meetings that are already booked across multiple rooms is a common scheduling challenge. Instead of dropping external items onto the calendar grid to create new events, the goal here is to drop them onto existing events, turning each meeting into an individual drop target where attendees can be assigned directly.

This is where the dropcontainer comes in. Each event rendered through the renderTimelineEvent function is initialized as a drop container, turning every meeting card into a valid drop zone. The external list of team members is set up using the draggable component, so each person can be grabbed and dropped onto any event on the timeline. The onItemDrop event handles the assignment, while onItemDragEnter and onItemDragLeave provide real-time visual feedback.

Beyond simple assignment, the demo includes practical validation. Before accepting a drop, it checks whether the person is already assigned to the target meeting and whether they have a time conflict with another meeting in the same slot. Assignments can also be removed by clicking on an attendee chip, with an undo option.

Looking for dropping external items onto the calendar grid instead?  Check out the external drag & drop example →
Download and try example

Demo

Loading the live demo

Preview in full screen

Build a team assignment interface on a Timeline — drag people from an external list onto booked meetings.

Code

mobiscroll.setOptions({
  locale:  ,
  theme: ' ',
  themeVariant: ' '
});

var rooms = [
  { id: 1, name: 'Conference Room' },
  { id: 2, name: 'Board Room' },
  { id: 3, name: 'Meeting Room' },
  { id: 4, name: 'Training Room' },
];

var meetings = [
  {
    id: 'evt1',
    title: 'Sprint Planning',
    start: '2026-10-20T09:00',
    end: '2026-10-20T11:00',
    resource: 1,
    color: '#b52db9',
    attendees: [
      { id: 'emp1', name: 'Alice Martin', avatar: 'AM', color: '#e74c3c' },
      { id: 'emp2', name: 'Bob Johnson', avatar: 'BJ', color: '#3498db' },
    ],
  },
  {
    id: 'evt2',
    title: 'Budget Review',
    start: '2026-10-20T10:00',
    end: '2026-10-20T13:00',
    resource: 2,
    color: '#669ce2',
    attendees: [],
  },
  {
    id: 'evt3',
    title: 'Client Presentation',
    start: '2026-10-20T15:00',
    end: '2026-10-20T18:00',
    resource: 2,
    color: '#88bd42',
    attendees: [
      { id: 'emp3', name: 'Carol Smith', avatar: 'CS', color: '#2ecc71' },
      { id: 'emp4', name: 'David Lee', avatar: 'DL', color: '#f39c12' },
    ],
  },
  {
    id: 'evt4',
    title: 'Project Kickoff',
    start: '2026-10-20T09:00',
    end: '2026-10-20T11:00',
    resource: 3,
    color: '#b6962f',
    attendees: [],
  },
  {
    id: 'evt5',
    title: 'New Hire Orientation',
    start: '2026-10-20T13:00',
    end: '2026-10-20T16:00',
    resource: 4,
    color: '#c864f0',
    attendees: [],
  },
  {
    id: 'evt6',
    title: 'Design Review',
    start: '2026-10-20T13:00',
    end: '2026-10-20T15:00',
    resource: 1,
    color: '#c7682d',
    attendees: [],
  },
  {
    id: 'evt7',
    title: 'Product Demo',
    start: '2026-10-21T09:00',
    end: '2026-10-21T11:00',
    resource: 1,
    color: '#ad2b6c',
    attendees: [],
  },
  {
    id: 'evt8',
    title: 'Stakeholder Update',
    start: '2026-10-21T10:00',
    end: '2026-10-21T12:00',
    resource: 2,
    color: '#0f60ca',
    attendees: [],
  },
  {
    id: 'evt9',
    title: 'Code Review',
    start: '2026-10-21T10:00',
    end: '2026-10-21T12:00',
    resource: 3,
    color: '#56a823',
    attendees: [],
  },
  {
    id: 'evt10',
    title: 'Safety Training',
    start: '2026-10-21T13:00',
    end: '2026-10-21T16:00',
    resource: 4,
    color: '#aa36d8',
    attendees: [],
  },
  {
    id: 'evt11',
    title: 'Retrospective',
    start: '2026-10-22T09:00',
    end: '2026-10-22T11:00',
    resource: 1,
    color: '#c45f20',
    attendees: [],
  },
  {
    id: 'evt12',
    title: 'Board Briefing',
    start: '2026-10-22T13:00',
    end: '2026-10-22T16:00',
    resource: 2,
    color: '#1e58a5',
    attendees: [],
  },
  {
    id: 'evt13',
    title: 'Marketing Sync',
    start: '2026-10-22T10:00',
    end: '2026-10-22T12:00',
    resource: 3,
    color: '#549e27',
    attendees: [],
  },
  {
    id: 'evt14',
    title: 'API Workshop',
    start: '2026-10-22T13:00',
    end: '2026-10-22T16:00',
    resource: 4,
    color: '#7c1ca1',
    attendees: [],
  },
  {
    id: 'evt15',
    title: 'Architecture Review',
    start: '2026-10-23T09:00',
    end: '2026-10-23T11:30',
    resource: 1,
    color: '#a7511c',
    attendees: [],
  },
  {
    id: 'evt16',
    title: 'Quarterly Planning',
    start: '2026-10-23T13:00',
    end: '2026-10-23T16:00',
    resource: 2,
    color: '#13488d',
    attendees: [],
  },
  {
    id: 'evt17',
    title: 'Hiring Debrief',
    start: '2026-10-23T10:00',
    end: '2026-10-23T12:00',
    resource: 3,
    color: '#51ac19',
    attendees: [],
  },
  {
    id: 'evt18',
    title: 'Team Standup',
    start: '2026-10-19T09:00',
    end: '2026-10-19T11:00',
    resource: 1,
    color: '#e74c3c',
    attendees: [],
  },
  {
    id: 'evt19',
    title: 'Daily Scrum',
    start: '2026-10-19T10:00',
    end: '2026-10-19T13:00',
    resource: 2,
    color: '#3498db',
    attendees: [],
  },
  {
    id: 'evt20',
    title: 'Lunch Meeting',
    start: '2026-10-19T12:00',
    end: '2026-10-19T14:00',
    resource: 3,
    color: '#2ecc71',
    attendees: [],
  },
  {
    id: 'evt21',
    title: 'Weekly Sync',
    start: '2026-10-19T14:00',
    end: '2026-10-19T16:00',
    resource: 4,
    color: '#f39c12',
    attendees: [],
  },
  {
    id: 'evt22',
    title: 'Morning Briefing',
    start: '2026-10-18T09:00',
    end: '2026-10-18T11:00',
    resource: 1,
    color: '#9b59b6',
    attendees: [],
  },
  {
    id: 'evt23',
    title: 'Status Update',
    start: '2026-10-18T11:00',
    end: '2026-10-18T14:00',
    resource: 2,
    color: '#1abc9c',
    attendees: [],
  },
  {
    id: 'evt24',
    title: 'Client Call',
    start: '2026-10-18T13:00',
    end: '2026-10-18T15:00',
    resource: 3,
    color: '#e67e22',
    attendees: [],
  },
  {
    id: 'evt25',
    title: 'End of Day Review',
    start: '2026-10-18T16:00',
    end: '2026-10-18T18:00',
    resource: 4,
    color: '#34495e',
    attendees: [],
  },
];

var employees = [
  { id: 'emp1', name: 'Alice Martin', avatar: 'AM', color: '#e74c3c' },
  { id: 'emp2', name: 'Bob Johnson', avatar: 'BJ', color: '#3498db' },
  { id: 'emp3', name: 'Carol Smith', avatar: 'CS', color: '#2ecc71' },
  { id: 'emp4', name: 'David Lee', avatar: 'DL', color: '#f39c12' },
  { id: 'emp5', name: 'Eva Chen', avatar: 'EC', color: '#9b59b6' },
  { id: 'emp6', name: 'Frank Diaz', avatar: 'FD', color: '#1abc9c' },
  { id: 'emp7', name: 'Grace Kim', avatar: 'GK', color: '#e67e22' },
  { id: 'emp8', name: 'Henry Patel', avatar: 'HP', color: '#34495e' },
  { id: 'emp9', name: 'Ivy Torres', avatar: 'IT', color: '#e84393' },
  { id: 'emp10', name: 'Jack Murphy', avatar: 'JM', color: '#0984e3' },
];

var container = document.querySelector('.mds-drop-on-events');

function getAssignmentCount(empId) {
  var count = 0;
  for (var i = 0; i < meetings.length; i++) {
    for (var j = 0; j < meetings[i].attendees.length; j++) {
      if (meetings[i].attendees[j].id === empId) {
        count++;
      }
    }
  }
  return count;
}

function findConflict(empId, targetEventId) {
  var target;
  for (var i = 0; i < meetings.length; i++) {
    if (meetings[i].id === targetEventId) {
      target = meetings[i];
      break;
    }
  }
  if (!target) {
    return null;
  }

  var targetStart = new Date(target.start).getTime();
  var targetEnd = new Date(target.end).getTime();

  for (var j = 0; j < meetings.length; j++) {
    var m = meetings[j];
    if (m.id === targetEventId) {
      continue;
    }

    var isAssigned = false;
    for (var k = 0; k < m.attendees.length; k++) {
      if (m.attendees[k].id === empId) {
        isAssigned = true;
        break;
      }
    }
    if (!isAssigned) {
      continue;
    }

    var mStart = new Date(m.start).getTime();
    var mEnd = new Date(m.end).getTime();

    if (mStart < targetEnd && mEnd > targetStart) {
      return m;
    }
  }

  return null;
}

function renderEmployeeList() {
  var list = document.getElementById('external-employee-list');
  list.innerHTML = '';

  for (var i = 0; i < employees.length; i++) {
    var emp = employees[i];
    var count = getAssignmentCount(emp.id);

    var item = document.createElement('div');
    item.className = 'mds-drop-on-events-employee-item mbsc-flex';
    item.id = 'mds-emp-' + emp.id;
    item.innerHTML =
      '<div class="mds-drop-on-events-employee-avatar mbsc-flex" style="background:' +
      emp.color +
      '">' +
      emp.avatar +
      '</div>' +
      '<div class="mds-drop-on-events-employee-info mbsc-flex">' +
      '<div class="mds-drop-on-events-employee-name">' +
      emp.name +
      '</div>' +
      (count > 0
        ? '<div class="mds-drop-on-events-employee-count">' + count + ' meeting' + (count > 1 ? 's' : '') + '</div>'
        : '<div class="mds-drop-on-events-employee-count">No assignments</div>') +
      '</div>';

    list.appendChild(item);

    mobiscroll.draggable('#mds-emp-' + emp.id, {
      dragData: emp,
    });
  }

  // Track drag start/end to toggle global dragging state
  if (list._mdsPointerDown) {
    list.removeEventListener('pointerdown', list._mdsPointerDown);
  }
  list._mdsPointerDown = function (e) {
    if (!e.target.closest('.mds-drop-on-events-employee-item')) {
      return;
    }
    function onMove() {
      container.classList.add('mds-drop-on-events-external-dragging');
      document.removeEventListener('pointermove', onMove);
    }
    function onUp() {
      container.classList.remove('mds-drop-on-events-external-dragging');
      document.removeEventListener('pointermove', onMove);
      document.removeEventListener('pointerup', onUp);
    }
    document.addEventListener('pointermove', onMove);
    document.addEventListener('pointerup', onUp);
  };
  list.addEventListener('pointerdown', list._mdsPointerDown);
}

function renderEvent(data) {
  var event = data.original;
  var attendees = event.attendees || [];

  var attendeesHtml = '';
  if (attendees.length > 0) {
    attendeesHtml = '<div class="mds-drop-on-events-event-attendees mbsc-flex">';
    for (var i = 0; i < attendees.length; i++) {
      attendeesHtml +=
        '<span class="mds-drop-on-events-attendee-chip" style="background:' +
        attendees[i].color +
        '" title="' +
        attendees[i].name +
        ' (click to remove)"' +
        ' data-emp-id="' +
        attendees[i].id +
        '">' +
        attendees[i].avatar +
        '<span class="mds-drop-on-events-attendee-remove">×</span>' +
        '</span>';
    }
    attendeesHtml += '</div>';
  }

  return (
    '<div class="mds-drop-on-events-custom-event mbsc-flex" id="mds-zone-' +
    event.id +
    '" style="border-left: 4px solid ' +
    event.color +
    '" data-event-id="' +
    event.id +
    '">' +
    '<div class="mds-drop-on-events-event-header mbsc-flex">' +
    '<div class="mds-drop-on-events-event-title">' +
    event.title +
    '</div>' +
    '<div class="mds-drop-on-events-event-time">' +
    data.start +
    ' - ' +
    data.end +
    '</div>' +
    '</div>' +
    attendeesHtml +
    '<div class="mds-drop-on-events-event-drop-hint">Drop people to assign</div>' +
    '</div>'
  );
}

function initDropZones() {
  var zones = document.querySelectorAll('.mds-drop-on-events-custom-event');
  for (var z = 0; z < zones.length; z++) {
    (function (zone) {
      var eventId = zone.getAttribute('data-event-id');

      mobiscroll.dropcontainer('#mds-zone-' + eventId, {
        onItemDrop: function (dropEvent) {
          var employee = dropEvent.data;

          zone.classList.remove('mds-drop-active', 'mds-drop-conflict');

          for (var i = 0; i < meetings.length; i++) {
            if (meetings[i].id === eventId) {
              var m = meetings[i];

              // Prevent duplicate assignment to the same event
              for (var k = 0; k < m.attendees.length; k++) {
                if (m.attendees[k].id === employee.id) {
                  mobiscroll.toast({
                    message: employee.name + ' is already assigned',
                    color: 'danger',
                  });
                  return;
                }
              }

              // Check for time conflicts
              var conflict = findConflict(employee.id, eventId);
              if (conflict) {
                mobiscroll.toast({
                  message: employee.name + ' already has a ' + conflict.title + ' on this timeslot',
                  color: 'danger',
                });
                return;
              }

              m.attendees.push({
                id: employee.id,
                name: employee.name,
                avatar: employee.avatar,
                color: employee.color,
              });

              setTimeout(function () {
                calendar.setEvents(meetings);
                renderEmployeeList();
              }, 0);

              mobiscroll.toast({
                message: employee.name + ' assigned to ' + m.title,
                color: 'success',
              });
              break;
            }
          }
        },
        onItemDragEnter: function (enterEvent) {
          var employee = enterEvent.data;

          zone.classList.remove('mds-drop-active', 'mds-drop-conflict');

          if (employee) {
            // Check if already assigned to this event
            var alreadyAssigned = false;
            for (var i = 0; i < meetings.length; i++) {
              if (meetings[i].id === eventId) {
                for (var k = 0; k < meetings[i].attendees.length; k++) {
                  if (meetings[i].attendees[k].id === employee.id) {
                    alreadyAssigned = true;
                    break;
                  }
                }
                break;
              }
            }

            if (alreadyAssigned) {
              zone.classList.add('mds-drop-conflict');
            } else {
              var conflict = findConflict(employee.id, eventId);
              if (conflict) {
                zone.classList.add('mds-drop-conflict');
              } else {
                zone.classList.add('mds-drop-active');
              }
            }
          } else {
            zone.classList.add('mds-drop-active');
          }
        },
        onItemDragLeave: function () {
          zone.classList.remove('mds-drop-active', 'mds-drop-conflict');
        },
      });
    })(zones[z]);
  }

  // Handle attendee removal on chip click
  var chips = document.querySelectorAll('.mds-drop-on-events-attendee-chip');
  for (var c = 0; c < chips.length; c++) {
    chips[c].addEventListener('click', function (e) {
      e.stopPropagation();
      var empId = this.getAttribute('data-emp-id');
      var eventEl = this.closest('.mds-drop-on-events-custom-event');
      var evtId = eventEl.getAttribute('data-event-id');

      for (var i = 0; i < meetings.length; i++) {
        if (meetings[i].id === evtId) {
          var attendees = meetings[i].attendees;
          for (var j = 0; j < attendees.length; j++) {
            if (attendees[j].id === empId) {
              var removedAttendee = attendees[j];
              var removedIndex = j;
              var meetingRef = meetings[i];

              attendees.splice(j, 1);
              calendar.setEvents(meetings);
              renderEmployeeList();

              (function (att, idx, meeting) {
                mobiscroll.snackbar({
                  message: att.name + ' removed from ' + meeting.title,
                  button: {
                    text: 'Undo',
                    action: function () {
                      meeting.attendees.splice(idx, 0, att);
                      calendar.setEvents(meetings);
                      renderEmployeeList();
                      mobiscroll.toast({
                        message: 'Assignment restored',
                        color: 'success',
                      });
                    },
                  },
                });
              })(removedAttendee, removedIndex, meetingRef);

              break;
            }
          }
          break;
        }
      }
    });
  }
}

renderEmployeeList();

var calendar = mobiscroll.eventcalendar('#demo-drop-assignee-on-event-from-list', {
  view: {
    timeline: {
      type: 'week',
      startDay: 1,
      endDay: 5,
      startTime: '08:00',
      endTime: '18:00',
      timeCellStep: 60,
      timeLabelStep: 60,
      virtualScroll: false,
    },
  },
  data: meetings,
  resources: rooms,
  dragToCreate: false,
  dragToMove: false,
  dragToResize: false,
  clickToCreate: false,
  eventDelete: false,
  showEventTooltip: false,
  renderTimelineEvent: renderEvent,
  onPageLoaded: initDropZones,
});
  

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.