Shifts and Tasks
Usage
theme: 'ios' You can turn auto theming on and let it decide based on the platform.
Usage
themeVariant: 'light' You can turn auto dark mode on based on system settings.
Usage
clickToCreate: true,
// click to create is on dragToCreate: true,
// drag to create is on dragToMove: true,
// reordering is on dragToResize: true,
// resizing is on eventDelete: true
// deleting is on Fine-tune drag & drop with these options, which are false by default.
Usage
locale: mobiscroll.localeEn Selecting the locale loads the localized settings and the translated UI text.
Shifts and Tasks
Visualize shifts and tasks within them using the timeline view. With the help of the order property of the data object you can always guarantee that shifts are always displayed at the top (alternatively below tasks) and tasks within those shifts are rendered below.
The shift and task events are bind together with custom data properties. Every shift contains the task IDs in the tasks property, and every task has a shift property which indicates the parent shift. These properties are used later in the validation logic to handle the two distinct event type behaviours. The validation is implemented by dynamically setting invalid ranges through the onEventCreated, onEventUpdated and onEventDeleted lifecycle events.
Demo

Code
mobiscroll.setOptions({
locale: ,
theme: ' ',
themeVariant: ' '
});
var myInvalids = [
{
start: '00:00',
end: '05:00',
recurring: {
repeat: 'daily',
},
workOff: true,
},
{
start: '19:00',
end: '00:00',
recurring: {
repeat: 'daily',
},
workOff: true,
},
];
var timelineInst = mobiscroll.eventcalendar('#demo-tasks-subtasks-under-shifts', {
dragToCreate: true,
dragToMove: true,
dragToResize: true,
view: {
timeline: {
type: 'week',
startDay: 1,
startTime: '05:00',
endTime: '19:00',
endDay: 5,
eventHeight: 'variable',
},
},
extendDefaultEvent: function (args) {
var events = timelineInst.getEvents(args.start, new Date(+args.start + 3600000)).filter(function (e) {
return e.resource === args.resource;
});
var isShift = events.length === 0;
return {
order: isShift ? 1 : 2,
cssClass: isShift ? 'mds-task-shift' : 'mds-task-subtask',
color: isShift ? '#513737' : '',
title: isShift ? 'New Shift' : 'New Task',
tasks: isShift ? [] : undefined,
shift: isShift ? undefined : events[0].id,
};
},
onEventCreated: function (args, inst) {
var event = args.event;
var overlapEvents = inst.getEvents(event.start, event.end).filter(function (e) {
return e.resource === event.resource;
});
if (event.shift) {
// Task was created
var shift = overlapEvents[0];
if (overlapEvents.length > 2) {
// Prevent task overlap
inst.removeEvent(event);
mobiscroll.toast({
message: 'No space for task',
});
} else {
// Update the shift
shift.tasks.push(event.id);
inst.updateEvent(shift);
// Update subtask
event.start = new Date(Math.max(+new Date(shift.start), +event.start));
event.end = new Date(Math.min(+new Date(shift.end), +event.end));
event.shift = shift.id;
inst.updateEvent(event);
}
}
},
onEventDeleted: function (args, inst) {
var event = args.event;
if (event.tasks) {
inst.removeEvent(event.tasks);
}
if (event.shift) {
var shift = inst.getEvents().find(function (ev) {
return ev.resource === event.resource && ev.id === event.shift;
});
// Remove the deleted task id from the shift data
shift.tasks = shift.tasks.filter(function (t) {
return t !== event.id;
});
inst.updateEvent(shift);
}
},
onEventDragStart: function (args, inst) {
var events = inst.getEvents();
var event = args.event;
var tempInvalid = [];
if (event.tasks) {
// Shift
var shiftsInResource = events.filter(function (e) {
return e.tasks !== undefined && e.resource === event.resource && e.id !== event.id;
});
shiftsInResource.forEach(function (e) {
tempInvalid.push({
start: e.start,
end: e.end,
resource: e.resource,
cssClass: 'mds-task-blocked',
shift: true,
});
});
inst.setOptions({
invalid: tempInvalid.concat(myInvalids),
});
} else {
// Subtask
var shift = events.find(function (ev) {
return ev.resource === event.resource && ev.id === event.shift;
});
tempInvalid.push(
{
start: new Date(+new Date(shift.start) - 7 * 86400000),
end: shift.start,
resource: shift.resource,
cssClass: 'mds-task-blocked',
task: true,
},
{
start: shift.end,
end: new Date(+new Date(shift.end) + 7 * 86400000),
resource: shift.resource,
cssClass: 'mds-task-blocked',
task: true,
},
);
inst.setOptions({
invalid: tempInvalid.concat(myInvalids),
});
}
},
onEventDragEnd: function (args, inst) {
inst.setOptions({
invalid: myInvalids,
});
},
onEventUpdated: function (args, inst) {
var events = inst.getEvents();
var event = args.event;
var oldEvent = args.oldEvent;
if (event.tasks) {
// Shift was updated
var shiftStart = new Date(event.start);
var shiftEnd = new Date(event.end);
var startDiff = +shiftStart - +new Date(oldEvent.start);
var endDiff = +shiftEnd - +new Date(oldEvent.end);
var startResize = startDiff > 0;
var endResize = endDiff < 0;
var isMove = startDiff === endDiff;
var isResize = !isMove && (startResize || endResize);
var subTasksDuration = 0;
var tasks = event.tasks.map(function (el) {
var t = events.find(function (e) {
return e.id === el;
});
subTasksDuration += +new Date(t.end) - +new Date(t.start);
return t;
});
if (isResize && +shiftEnd - +shiftStart < subTasksDuration) {
// Limit the shift to don't be smaller than the containing subtasks
shiftStart = endResize ? shiftStart : new Date(+shiftEnd - subTasksDuration);
shiftEnd = startResize ? shiftEnd : new Date(+shiftStart + subTasksDuration);
event.start = shiftStart;
event.end = shiftEnd;
inst.updateEvent(event);
}
// Resize or move
if (isResize || startDiff === endDiff) {
var updatedTasks = [];
// Update subtask
tasks.forEach(function (task, i) {
var taskStart = new Date(task.start);
var taskEnd = new Date(task.end);
if (isResize) {
task.start = new Date(i === 0 ? +shiftStart : +new Date(tasks[i - 1].end));
task.end = new Date(Math.min(+task.start + (+taskEnd - +taskStart), +new Date(event.end)));
} else {
task.start = new Date(+taskStart + startDiff);
task.end = new Date(+taskEnd + startDiff);
}
updatedTasks.push(task);
});
inst.updateEvent(updatedTasks);
}
} else {
// Subtask was updated
var eventOverlap = inst.getEvents(event.start, event.end).filter(function (e) {
return e.resource === event.resource;
});
if (eventOverlap.length > 2) {
// Don't let subtask to overlap
inst.updateEvent(oldEvent);
mobiscroll.toast({
message: 'No space for task',
});
}
}
},
onEventUpdateFailed: function (args) {
var invalid = args.invalid;
var msg = '';
if (invalid.workOff) {
msg = 'Shift falls out of working hours';
} else if (invalid.task) {
msg = 'Task falls out of shift';
} else if (invalid.shift) {
msg = 'Shifts cannot overlap';
}
msg &&
mobiscroll.toast({
message: msg,
});
},
renderTimelineEventContent: function (args) {
var duration = (+args.endDate - +args.startDate) / 3600000;
return (
args.title +
(args.original.shift ? ' <span class="mds-task-hours"> - ' + (duration + (duration === 1 ? 'h' : 'hrs')) + '</span>' : '')
);
},
data: [
{
id: 1,
start: '2026-10-25T05:00',
end: '2026-10-25T12:00',
title: 'Daily Shift',
resource: 1,
tasks: ['es-1', 'es-2', 'es-3'],
order: 1,
color: '#513737',
cssClass: 'mds-task-shift',
},
{
id: 'es-1',
start: '2026-10-25T05:00',
end: '2026-10-25T08:30',
title: 'Server Maintenance',
resource: 1,
shift: 1,
order: 2,
cssClass: 'mds-task-subtask',
},
{
id: 'es-2',
start: '2026-10-25T09:00',
end: '2026-10-25T10:45',
title: 'Monitor System Performance',
resource: 1,
shift: 1,
order: 2,
cssClass: 'mds-task-subtask',
},
{
id: 'es-3',
start: '2026-10-25T11:00',
end: '2026-10-25T12:00',
title: 'Backup and Recovery',
resource: 1,
shift: 1,
order: 2,
cssClass: 'mds-task-subtask',
},
{
id: 2,
start: '2026-10-25T11:00',
end: '2026-10-25T18:00',
title: 'Flex Shift',
resource: 2,
tasks: ['jb-1', 'jb-2', 'jb-3', 'jb-4'],
order: 1,
color: '#513737',
cssClass: 'mds-task-shift',
},
{
id: 'jb-1',
start: '2026-10-25T11:00',
end: '2026-10-25T12:30',
title: 'Code Review',
resource: 2,
shift: 2,
order: 2,
cssClass: 'mds-task-subtask',
},
{
id: 'jb-2',
start: '2026-10-25T13:00',
end: '2026-10-25T14:45',
title: 'Develop New Features',
resource: 2,
shift: 2,
order: 2,
cssClass: 'mds-task-subtask',
},
{
id: 'jb-3',
start: '2026-10-25T15:00',
end: '2026-10-25T16:50',
title: 'Mentor Junior Developers',
resource: 2,
shift: 2,
order: 2,
cssClass: 'mds-task-subtask',
},
{
id: 'jb-4',
start: '2026-10-25T17:00',
end: '2026-10-25T18:00',
title: 'Attend Stand-Up Meeting',
resource: 2,
shift: 2,
order: 2,
cssClass: 'mds-task-subtask',
},
],
resources: [
{
id: 1,
color: '#ff0101',
name: 'Emma Smith',
eventDragBetweenResources: false,
},
{
id: 2,
color: '#239a21',
name: 'James Brown',
eventDragBetweenResources: false,
},
{
id: 3,
color: '#8f1ed6',
name: 'Olivia Miller',
eventDragBetweenResources: false,
},
{
id: 4,
color: '#01adff',
name: 'Robert Taylor',
eventDragBetweenResources: false,
},
{
id: 5,
color: '#F58585',
name: 'John Doe',
eventDragBetweenResources: false,
},
],
invalid: myInvalids,
});
<div id="demo-tasks-subtasks-under-shifts"></div>
.mds-task-shift {
height: 30px;
}
.mds-task-subtask {
height: 40px;
}
.mds-task-subtask .mbsc-schedule-event-range,
.mds-task-subtask .mbsc-schedule-event-title {
font-size: 10px;
line-height: 13px;
}
.mds-task-shift .mbsc-schedule-event-range,
.mds-task-shift .mbsc-schedule-event-title {
display: inline-block;
margin: 0 4px;
}
.mds-task-hours {
font-size: 10px;
font-weight: 400;
}
.mds-task-blocked.mbsc-schedule-invalid {
text-align: center;
align-items: center;
font-weight: bold;
background: repeating-linear-gradient(-45deg, #f3f3f3, #f3f3f3 11px, #e5e5e5 11px, #e5e5e5 22px);
}