---
title: "JQuery Timeline Employee Shift Scheduling Example | Mobiscroll"
component: "Timeline"
framework: "jQuery"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/jquery/timeline/employee-shifts"
---

# Employee Shift Scheduling - jQuery Timeline demo

Plan employee shifts on a JQuery Timeline — show workers as resource rows with morning and afternoon slots, and manage shifts with a custom add/edit dialog. For jQuery or jQuery Mobile.

- [Live demo](https://demo.mobiscroll.com/jquery/timeline/employee-shifts)
- [View code on GitHub](https://github.com/acidb/mobiscroll-demos-jquery/tree/main/src/demos/eventcalendar/timeline/employee-shifts)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/javascript/timeline/employee-shifts.md), [Angular](https://demo.mobiscroll.com/angular/timeline/employee-shifts.md), [React](https://demo.mobiscroll.com/react/timeline/employee-shifts.md), [Vue](https://demo.mobiscroll.com/vue/timeline/employee-shifts.md)

## Description

Use the summary mode of the timeline view with `eventDisplay: 'fill'` that can be configured under the `timeline` settings of the [`view`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-view) option.

This will give you summarized daily events similar to the [event calendar labels](https://demo.mobiscroll.com/jquery/eventcalendar/event-labels) where each event is printed under the appropriate day, time slot (shift) for the appropriate resource one after the other.

Build a custom add/edit dialog with the necessary fields. The event dialog can be fully custom as seen in this [CRUD example](https://demo.mobiscroll.com/jquery/scheduler/create-read-update-delete-CRUD).

Customize the dialog header with a custom header renderer function using the [`renderHeader`](https://mobiscroll.com/docs/jquery/eventcalendar/api#renderer-renderHeader) option.

## What this demo shows

- A weekly timeline-based shift planner that displays employees as resource rows and shows only work days.
- **Shift structure** Each day is divided into two named slots, Morning and Afternoon, instead of showing shifts by exact position on a time grid.
- **Resources** Each row represents an employee, and the events shown in that row are the scheduled shifts for that employee.
- **Visual distinction** Each employee has an assigned color, and their shifts use that color in the timeline.
- **Editing shifts** Clicking an existing shift opens a popup with an edit form where the shift start and end times can be changed and notes can be added.
- **Deleting shifts** The edit popup includes a delete action that removes the shift immediately, then shows a snackbar confirmation with an Undo action to restore it.
- **Creating shifts** Double-clicking an empty timeline slot opens a popup for adding a new shift.
- **Add form** The create popup lets users set the shift start and end times and add notes before saving.

## Best for

- **Employee shift planning** Weekly rota and shift scheduling interfaces where employees are shown as resources.
- **Work-week scheduling** Setups that focus on working days only rather than a full seven-day timeline.
- **Named-shift layouts** Scenarios where shifts are organized into fixed slots such as Morning and Afternoon instead of being positioned by exact time.
- **Color-coded teams** Calendars where resource-based color coding helps users quickly identify which shifts belong to which employee.
- **Direct calendar editing** Applications where users need to create, update, delete, and restore shifts directly from the calendar UI.

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-themeVariant) - option
- [`setOptions`](https://mobiscroll.com/docs/jquery/datepicker/api#method-setOptions) - method
- [`minTime`](https://mobiscroll.com/docs/jquery/datepicker/api#opt-minTime) - option
- [`maxTime`](https://mobiscroll.com/docs/jquery/datepicker/api#opt-maxTime) - option
- [`updateEvent`](https://mobiscroll.com/docs/jquery/eventcalendar/api#method-updateEvent) - method
- [`close`](https://mobiscroll.com/docs/jquery/popup/api#method-close) - method
- [`setOptions`](https://mobiscroll.com/docs/jquery/popup/api#method-setOptions) - method
- [`buttons`](https://mobiscroll.com/docs/jquery/popup/api#opt-buttons) - option
- [`renderHeader`](https://mobiscroll.com/docs/jquery/popup/api#renderer-renderHeader) - renderer
- [`onClose`](https://mobiscroll.com/docs/jquery/popup/api#event-onClose) - event
- [`removeEvent`](https://mobiscroll.com/docs/jquery/eventcalendar/api#method-removeEvent) - method
- [`open`](https://mobiscroll.com/docs/jquery/popup/api#method-open) - method
- [`cssClass`](https://mobiscroll.com/docs/jquery/popup/api#opt-cssClass) - option
- [`clickToCreate`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-clickToCreate) - option
- [`dragToCreate`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-dragToCreate) - option
- [`dragToResize`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-dragToResize) - option
- [`dragToMove`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-dragToMove) - option
- [`data`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-data) - option
- [`eventOverlap`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-eventOverlap) - option
- [`invalid`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-invalid) - option
- [`resources`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-resources) - option
- [`slots`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-slots) - option
- [`view`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-view) - option
- [`extendDefaultEvent`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-extendDefaultEvent) - option
- [`renderResource`](https://mobiscroll.com/docs/jquery/eventcalendar/api#renderer-renderResource) - renderer
- [`onEventClick`](https://mobiscroll.com/docs/jquery/eventcalendar/api#event-onEventClick) - event
- [`onEventCreated`](https://mobiscroll.com/docs/jquery/eventcalendar/api#event-onEventCreated) - event
- [`onEventCreateFailed`](https://mobiscroll.com/docs/jquery/eventcalendar/api#event-onEventCreateFailed) - event
- [`onEventUpdated`](https://mobiscroll.com/docs/jquery/eventcalendar/api#event-onEventUpdated) - event
- [`onEventUpdateFailed`](https://mobiscroll.com/docs/jquery/eventcalendar/api#event-onEventUpdateFailed) - event
- [`display`](https://mobiscroll.com/docs/jquery/popup/api#opt-display) - option
- [`fullScreen`](https://mobiscroll.com/docs/jquery/popup/api#opt-fullScreen) - option
- [`responsive`](https://mobiscroll.com/docs/jquery/popup/api#opt-responsive) - option
- [`touchUi`](https://mobiscroll.com/docs/jquery/popup/api#opt-touchUi) - option
- [`width`](https://mobiscroll.com/docs/jquery/popup/api#opt-width) - option
- [`scrollLock`](https://mobiscroll.com/docs/jquery/popup/api#opt-scrollLock) - option
- [`onOpen`](https://mobiscroll.com/docs/jquery/popup/api#event-onOpen) - event
- [`controls`](https://mobiscroll.com/docs/jquery/datepicker/api#opt-controls) - option
- [`display`](https://mobiscroll.com/docs/jquery/datepicker/api#opt-display) - option
- [`select`](https://mobiscroll.com/docs/jquery/datepicker/api#opt-select) - option
- [`showRangeLabels`](https://mobiscroll.com/docs/jquery/datepicker/api#opt-showRangeLabels) - option
- [`stepMinute`](https://mobiscroll.com/docs/jquery/datepicker/api#opt-stepMinute) - option
- [`startInput`](https://mobiscroll.com/docs/jquery/datepicker/api#opt-startInput) - option
- [`endInput`](https://mobiscroll.com/docs/jquery/datepicker/api#opt-endInput) - option
- [`timeWheels`](https://mobiscroll.com/docs/jquery/datepicker/api#localization-timeWheels) - option
- [`touchUi`](https://mobiscroll.com/docs/jquery/datepicker/api#opt-touchUi) - option
- [`addEvent`](https://mobiscroll.com/docs/jquery/eventcalendar/api#method-addEvent) - method

## Code

### JS

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

$(function () {
  function getShiftTimes(event) {
    var d = event.start;
    var start = new Date(d.getFullYear(), d.getMonth(), d.getDate(), event.slot == 1 ? 7 : 12);
    var end = new Date(d.getFullYear(), d.getMonth(), d.getDate(), event.slot == 1 ? 13 : 18);

    return {
      title: formatDate('HH:mm', start) + ' - ' + formatDate('HH:mm', end),
      start: start,
      end: end,
    };
  }

  function fillPopup(event) {
    $shiftNotes.mobiscroll('getInst').value = event.notes || '';
    eventStartEndPicker.setOptions({ minTime: event.slot == 1 ? '07:00' : '12:00', maxTime: event.slot == 1 ? '13:00' : '18:00' });
    eventStartEndPicker.setVal([new Date(event.start), new Date(event.end)]);
  }

  function saveEvent(event) {
    var dates = eventStartEndPicker.getVal();
    var start = dates[0];
    var end = dates[1] ? dates[1] : dates[0];

    var shiftStart = new Date(event.start);
    var shiftEnd = new Date(event.end);

    shiftStart.setHours(start.getHours(), start.getMinutes(), 0, 0);
    shiftEnd.setHours(end.getHours(), end.getMinutes(), 0, 0);

    event.start = shiftStart;
    event.end = shiftEnd;
    event.title = formatDate('HH:mm', start) + ' - ' + formatDate('HH:mm', end);
    event.notes = $shiftNotes.val();

    calendar.updateEvent(event);
    addEditPopup.close();
  }

  function createAddPopup(args) {
    var event = args.event;
    var success = false;

    var slot = args.slotObj;

    // Hide delete button inside add popup
    $shiftDeleteButton.parent().hide();

    shift = event;

    // Set popup header text and buttons
    addEditPopup.setOptions({
      buttons: [
        'cancel',
        {
          text: 'Add',
          keyCode: 'enter',
          handler: function () {
            success = true;
            saveEvent(event);
          },
          addEditPopup: 'mbsc-popup-button-primary',
        },
      ],
      renderHeader: function () {
        return (
          '<div class="mds-employee-shifts-header-primary">New shift</div>' +
          '<div class="mds-employee-shifts-header-date">' +
          formatDate('DDDD', new Date(event.start)) +
          ' ' +
          slot.name +
          ', ' +
          formatDate('D MMMM YYYY', new Date(event.start)) +
          '</div>'
        );
      },
      onClose: function () {
        // Remove event if popup is cancelled
        if (!success) {
          calendar.removeEvent(event);
        }
      },
    });

    addEditPopup.open();
  }

  function createEditPopup(args) {
    var event = args.event;
    // Show delete button inside edit popup
    $shiftDeleteButton.parent().show();

    shift = event;

    var resource = args.resourceObj;

    var slot = args.slotObj;

    fillPopup(event);

    addEditPopup.setOptions({
      buttons: [
        'cancel',
        {
          text: 'Save',
          keyCode: 'enter',
          handler: function () {
            saveEvent(event);
          },
          cssClass: 'mbsc-popup-button-primary',
        },
      ],
      renderHeader: function () {
        return (
          '<div class="mds-employee-shifts-header-primary">Edit ' +
          resource.name +
          '\'s hours</div><div class="mds-employee-shifts-header-date">' +
          formatDate('DDDD', new Date(event.start)) +
          ' ' +
          slot.name +
          ', ' +
          formatDate('D MMMM YYYY', new Date(event.start)) +
          '</div>'
        );
      },
    });

    addEditPopup.open();
  }

  var formatDate = mobiscroll.formatDate;

  var shift;

  var $shiftNotes = $('#demo-popup-shift-notes');
  var $shiftDeleteButton = $('#demo-popup-shift-delete');

  var staff = [
    {
      id: 1,
      name: 'Ryan',
      color: '#e20000',
      title: 'Cloud System Engineer',
      img: 'https://img.mobiscroll.com/demos/m1.png',
    },
    {
      id: 2,
      name: 'Kate',
      color: '#60e81a',
      title: 'Help Desk Specialist',
      img: 'https://img.mobiscroll.com/demos/f1.png',
    },
    {
      id: 3,
      name: 'John',
      color: '#3ba7ff',
      title: 'Application Developer',
      img: 'https://img.mobiscroll.com/demos/m2.png',
    },
    {
      id: 4,
      name: 'Mark',
      color: '#e25dd2',
      title: 'Network Administrator',
      img: 'https://img.mobiscroll.com/demos/m3.png',
    },
    {
      id: 5,
      name: 'Sharon',
      color: '#f1e920',
      title: 'Data Quality Manager',
      img: 'https://img.mobiscroll.com/demos/f2.png',
    },
    {
      id: 6,
      name: 'Emma',
      color: '#1ac38d',
      title: 'Product Tactics Agent',
      img: 'https://img.mobiscroll.com/demos/f3.png',
    },
    {
      id: 7,
      name: 'Michael',
      color: '#f18a1a',
      title: 'UX Designer',
      img: 'https://img.mobiscroll.com/demos/m4.png',
    },
    {
      id: 8,
      name: 'Laura',
      color: '#7b61ff',
      title: 'DevOps Engineer',
      img: 'https://img.mobiscroll.com/demos/f4.png',
    },
  ];

  var shifts = [
    {
      start: '2026-10-17T07:00',
      end: '2026-10-17T13:00',
      title: '07:00 - 13:00',
      resource: 1,
      slot: 1,
    },
    {
      start: '2026-10-17T07:00',
      end: '2026-10-17T13:00',
      title: '07:00 - 13:00',
      resource: 7,
      slot: 1,
    },
    {
      start: '2026-10-17T12:00',
      end: '2026-10-17T18:00',
      title: '12:00 - 18:00',
      resource: 3,
      slot: 2,
    },
    {
      start: '2026-10-17T12:00',
      end: '2026-10-17T18:00',
      title: '12:00 - 18:00',
      resource: 8,
      slot: 2,
    },
    {
      start: '2026-10-18T07:00',
      end: '2026-10-18T13:00',
      title: '07:00 - 13:00',
      resource: 4,
      slot: 1,
    },
    // Showing partial data. Download full source.
  ];

  var mySlots = [
    { id: 1, name: 'Morning' },
    { id: 2, name: 'Afternoon' },
  ];

  var myInvalids = [
    {
      start: '2026-10-22T00:00',
      end: '2026-10-22T23:59',
      resource: 4,
      slot: 1,
    },
    {
      start: '2026-10-20T00:00',
      end: '2026-10-20T23:59',
      resource: 2,
      slot: 2,
    },
  ];

  var calendar = $('#demo-employee-shifts-calendar')
    .mobiscroll()
    .eventcalendar({
      clickToCreate: true,
      dragToCreate: false,
      dragToResize: false,
      dragToMove: true,
      data: shifts,
      eventOverlap: false,
      invalid: myInvalids,
      resources: staff,
      slots: mySlots,
      view: {
        timeline: {
          type: 'week',
          eventDisplay: 'fill',
          startDay: 1,
          endDay: 5,
        },
      },
      extendDefaultEvent: getShiftTimes,
      renderResource: function (resource) {
        return (
          '<div class="mbsc-flex">' +
          '<img alt="' +
          resource.name +
          '" class="mds-employee-shifts-avatar" src="' +
          resource.img +
          '"/>' +
          '<div class="mds-employee-shifts-cont">' +
          '<div class="mds-employee-shifts-name">' +
          resource.name +
          '</div>' +
          '<div class="mds-employee-shifts-title">' +
          resource.title +
          '</div>' +
          '</div>' +
          '</div>'
        );
      },
      onEventClick: function (args) {
        createEditPopup(args);
      },
      onEventCreated: function (args) {
        createAddPopup(args);
      },
      onEventCreateFailed: function () {
        mobiscroll.toast({
          message: "Can't create shift",
        });
      },
      onEventUpdated: function (args) {
        var event = args.event;
        if (event.slot !== args.oldEvent.slot) {
          var data = getShiftTimes(event);
          event.start = data.start;
          event.end = data.end;
          event.title = data.title;
          calendar.updateEvent(event);
        }
      },
      onEventUpdateFailed: function () {
        mobiscroll.toast({
          message: "Can't move shift",
        });
      },
    })
    .mobiscroll('getInst');

  var addEditPopup = $('#demo-employee-shifts-popup')
    .mobiscroll()
    .popup({
      display: 'bottom',
      contentPadding: false,
      fullScreen: true,
      responsive: {
        medium: {
          display: 'center',
          fullScreen: false,
          touchUi: false,
          width: 400,
        },
      },
      scrollLock: false,
      onOpen: function () {
        fillPopup(shift);
      },
    })
    .mobiscroll('getInst');

  var eventStartEndPicker = $('#demo-popup-shift-dates')
    .mobiscroll()
    .datepicker({
      controls: ['time'],
      display: 'anchored',
      select: 'range',
      showRangeLabels: false,
      stepMinute: 30,
      startInput: '#demo-popup-shift-start',
      endInput: '#demo-popup-shift-end',
      timeWheels: '|h:mm A|',
      touchUi: false,
    })
    .mobiscroll('getInst');

  $shiftDeleteButton.on('click', function () {
    calendar.removeEvent(shift);

    addEditPopup.close();

    // Save a local reference to the deleted event
    var deletedEvent = shift;

    mobiscroll.snackbar({
      button: {
        action: function () {
          calendar.addEvent(deletedEvent);
        },
        text: 'Undo',
      },
      message: 'Shift deleted',
    });
  });
});
```

### HTML

```html
<div id="demo-employee-shifts-calendar" class="mds-employee-shifts"></div>

<div style="display: none;">
  <div id="demo-employee-shifts-popup">
    <div>
      <div id="demo-popup-shift-dates"></div>
    </div>
    <div class="mbsc-form-group">
      <label>
        <input mbsc-input data-label="Shift start" data-dropdown="true" id="demo-popup-shift-start" />
      </label>
      <label>
        <input mbsc-input data-label="Shift end" data-dropdown="true" id="demo-popup-shift-end" />
      </label>
    </div>
    <div class="mbsc-form-group">
      <label>
        <textarea mbsc-textarea data-label="Notes" id="demo-popup-shift-notes"></textarea>
      </label>
    </div>
    <div class="mbsc-button-group">
      <button class="mbsc-button-block" id="demo-popup-shift-delete" mbsc-button data-color="danger" data-variant="outline">Delete shift</button>
    </div>
  </div>
</div>
```

### CSS

```css
.mds-employee-shifts-header-primary {
  font-size: 16px;
  line-height: 20px;
}

.mds-employee-shifts-header-date {
  font-size: 14px;
  font-weight: 600;
  line-height: 20px;
  opacity: .6;
}

.mds-employee-shifts .mbsc-timeline-resource-col {
  width: 200px;
}

.mds-employee-shifts .mbsc-schedule-event-inner {
  text-align: center;
  height: 36px;
  line-height: 36px;
}

.mds-employee-shifts-cont {
  padding: 0 7px;
}

.mds-employee-shifts-name {
  font-size: 14px;
  line-height: 24px;
}

.mds-employee-shifts-title {
  font-size: 12px;
  font-weight: 400;
  line-height: 16px;
}

.mds-employee-shifts-avatar {
  width: 40px;
  height: 40px;
}
```

## Implementation notes

- Use the timeline view in summary mode with `eventDisplay: 'fill'` to render shift events within resource rows and named slot buckets. Set `startDay: 1` and `endDay: 5` to show only work days.
- Define the Morning and Afternoon shift buckets with the `slots` option. Each slot object needs an `id` and a `name`, and each event references its slot via the `slot` field.
- Give each employee a `color` property on the resource object. Mobiscroll automatically applies the resource color to its events, so no extra event-level styling is needed.
- Use `renderResource` (Angular: `resourceTemplate`, Vue: `#resource` slot) to display a custom resource row with the employee's avatar image, name, and job title.
- Enable `clickToCreate` so users can create a new shift by clicking an empty cell. Set `dragToMove: true` so existing shifts can be repositioned by dragging, and set `dragToCreate: false` and `dragToResize: false` to prevent accidental event creation by drag and to keep shift boundaries tied to the slot definition.
- Use `extendDefaultEvent` to set the correct start and end times on every newly created event based on which slot it lands in - for example, Morning maps to 07:00-13:00 and Afternoon to 12:00-18:00.
- Set `eventOverlap: false` to block placing a second shift for the same employee in the same slot on the same day.
- Use the `invalid` option to mark specific resource-slot-day combinations where shifts cannot be created or moved, such as an employee's day off or a slot they are not eligible for.
- Use `onEventCreated` to intercept the newly created event, populate the popup with the slot-derived time range, and open the add form. The `slotObj` in the event args provides the slot name for the popup header.
- Use `onEventClick` to open the edit form for an existing shift. Read `resourceObj` and `slotObj` from the event args to build a descriptive popup header showing the employee name, day, and slot.
- Use `onEventUpdated` to detect when a shift has been dragged from one slot to another. When the slot changes, recalculate and overwrite the event's start, end, and title to match the new slot's fixed time range.
- Use `onEventCreateFailed` and `onEventUpdateFailed` to show a toast message when a shift cannot be placed or moved, for example because the target cell is marked invalid or blocked by `eventOverlap`.
- Build the add/edit popup with the `Popup` component. Use the `responsive` option to switch from a full-screen bottom sheet on mobile to a centered fixed-width dialog on larger screens.
- Render a custom popup header with a primary line - such as "Edit Ryan's hours" or "New shift" - and a secondary line with the day name, slot name, and date.
- Inside the popup, use a `Datepicker` in range mode with `controls={['time']}`. Connect it to two `Input` fields via `startInput` and `endInput` refs so the start and end times appear as separate labelled inputs that open the shared range picker. Constrain the allowed range with `minTime` and `maxTime` derived from the current slot.
- For deletion, add a Delete button to the edit popup that removes the shift immediately and closes the popup, then shows a `Snackbar` with an Undo action that restores the deleted shift if the user reverses the action.

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

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

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

## Related demos

- Next: [Work Order Scheduling](https://demo.mobiscroll.com/jquery/timeline/work-order-scheduling.md) - Schedule work orders across grouped resources — drivers, trucks, and contractors — on a JQuery timeline.
