---
title: "Javascript Scheduler Appointment Scheduling Example | Mobiscroll"
component: "Scheduler"
framework: "JavaScript"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/javascript/scheduler/doctors-appointment"
---

# Appointment Scheduling - JavaScript Scheduler demo

Build a Javascript appointment scheduler — assign doctors or dentists, drag from an unscheduled queue, and prevent double-booking. Plain JS api for usage everywhere.

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

## Description

The scheduler can be set up for scheduling/unscheduling booked appointments. Simply set up a daily schedule view with multiple resources representing dentists or doctors. Next to the calendar you can render a list of unscheduled appointments.

Enabling [advanced validation](https://demo.mobiscroll.com/javascript/scheduler/time-off-blocked-ranges) and combining that with the [drag & drop between the calendar and external list](https://demo.mobiscroll.com/javascript/scheduler/external-drag-drop-sortable-dragula) on a resource to resource basis you got a pretty nice working base for appointment scheduling that can be further customized.

## What this demo shows

- A desktop daily scheduler for doctor or dentist appointments, with date navigation, multiple resources, a scrollable time grid, and an external list of unscheduled appointments.
- **Header navigation** The month and year label in the top left opens date navigation, while the blue previous and next arrows and the Today button on the right make it easy to move between dates and jump back to the current day.
- **Day header** The curent or selected date and short weekday name are shown below the main navigation.
- **Resource strip** Multiple resources are shown below the date header, representing dentists or doctors.
- **Time grid** The scheduler displays a vertical day grid from 8AM to 8PM for the selected day.
- **Current time** A blue horizontal line marks the current time across the scheduler.
- **Past hours** Time ranges before the current time are disabled and shown with a gray background.
- **Disabled past interactions** Disabled past hours block event creation and drag-and-drop interactions in those time ranges.
- **Past events** Events scheduled in past hours remain visible, but they cannot be moved or repositioned.
- **Failed past updates** When a user tries to create or move an event into a past time range, a bottom-center toast appears with the message: `Can't add event in the past`.
- **Hover feedback** Hovering the time grid shows a time indicator that follows the pointer in 15-minute increments.
- **External task list** A right-side panel titled `Unscheduled appointments` shows four unscheduled appointments by default with different colors: `Winfred Lesley - Teeth whitening 1.5 hours` - yellow, `Rosaline Delice - Crown and bridge 2 hours` - green, `Macy Steven - Root canal treatment` - red,`Lavern Cameron / Rartar removal 1 hour` - purple.
- **External drag and drop** Appointments can be dragged from the external list onto the scheduler to add appointments.
- **Unscheduling events** Scheduled appointments can be dragged from the scheduler and dropped back onto the external list.
- **Confirmation messages** When an appointment is scheduled from the external list, a bottom-center toast confirms that the client was added. When an appointment is moved back to the external list, a toast confirms that the client was unscheduled.
- **External task list state** When a scheduled event is dragged over the external list, the panel background changes to light green.
- **Event rendering** Events appear as colored cards with a colored stripe on the left, the event time above the title which is the patient name shown in bold.
- **Event overlapping** Events cannot overlap. If a user tries to create, move, or resize an event and it overlaps another scheduled event, a bottom-center toast appears with the message: `Make sure not to double book`.
- **Event interactions** Hovering over a future event highlights it and shows drag and resize handles, indicating that it can be moved or resized.
- **Event creation** On future hours, double-clicking the time grid or clicking and dragging on it creates a new event and shows a bottom-center toast with the message: `New event added`.
- **Event selection** Clicking an event selects and highlights it.

## Best for

- **Dental clinics and dental practices** Schedule patients with specific dentists or hygienists while keeping new or unconfirmed appointments in an unscheduled queue until a resource becomes available.
- **Medical offices and healthcare providers** Manage consultations, treatments, examinations, and follow-up visits across multiple doctors, nurses, or specialists.
- **Veterinary clinics** Coordinate appointments across veterinarians, treatment rooms, and support staff while tracking patients waiting to be scheduled.
- **Beauty salons and spas** Assign customers to stylists, therapists, or treatment rooms and drag unscheduled bookings into available time slots.
- **Physical therapy and rehabilitation centers** Schedule therapists, treatment sessions, and equipment while maintaining visibility into pending appointments.
- **Automotive service centers** Organize repair jobs, inspections, and maintenance appointments across service technicians and service bays.
- **Professional service businesses** Manage consultations, assessments, interviews, or client meetings across multiple specialists or advisors.
- **Home services and field operations** Schedule technicians, installers, inspectors, or maintenance crews and assign incoming jobs from a backlog.
- **Call centers and customer appointment teams** Coordinate customer appointments across available agents while tracking unassigned requests.
- **Educational and tutoring services** Match students with instructors, classrooms, or training resources and schedule sessions as availability opens up.

## 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
- [`view`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-view) - option
- [`data`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-data) - option
- [`resources`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-resources) - option
- [`invalid`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-invalid) - option
- [`dragToMove`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-dragToMove) - option
- [`dragToCreate`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-dragToCreate) - option
- [`eventOverlap`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-eventOverlap) - option
- [`externalDrop`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-externalDrop) - option
- [`externalDrag`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-externalDrag) - option
- [`extendDefaultEvent`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-extendDefaultEvent) - option
- [`onEventCreate`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventCreate) - event
- [`setOptions`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-setOptions) - method
- [`colors`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-colors) - option
- [`onEventCreated`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventCreated) - event
- [`onEventCreateFailed`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventCreateFailed) - event
- [`onEventUpdateFailed`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventUpdateFailed) - event
- [`onEventDelete`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventDelete) - event
- [`onEventDragEnter`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventDragEnter) - event
- [`onEventDragLeave`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventDragLeave) - event

## Code

### JS

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

var externalCont = document.getElementById('md-docs-appointment-cont');
var now = new Date();
var today = new Date(now.setMinutes(59));
var yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);

var myData = [
  {
    id: 'job1',
    start: '2026-10-22T14:00',
    end: '2026-10-22T16:00',
    resource: 1,
    title: 'Myla Bennett',
    job: 'Wisdom tooth removal',
    color: '#334ab9',
  },
  {
    id: 'job2',
    start: '2026-10-22T17:00',
    end: '2026-10-22T18:30',
    resource: 1,
    title: 'Beatrix Foley',
    job: 'Braces',
    color: '#177e70',
  },
  {
    id: 'job3',
    start: '2026-10-22T08:00',
    end: '2026-10-22T09:30',
    resource: 3,
    title: 'Frank Watson',
    job: 'Teeth whitening',
    color: '#d1891f',
  },
  {
    id: 'job4',
    start: '2026-10-22T10:00',
    end: '2026-10-22T12:30',
    resource: 3,
    title: 'Jaime Joyce',
    job: 'Root canal treatment',
    color: '#cb3939',
  },
  {
    id: 'job5',
    start: '2026-10-22T13:00',
    end: '2026-10-22T14:00',
    resource: 3,
    title: 'Corey Shepard',
    job: 'Tooth extraction',
    color: '#aba343',
  },
  {
    id: 'job6',
    start: '2026-10-22T14:00',
    end: '2026-10-22T16:00',
    resource: 4,
    title: 'Callie Leonard',
    job: 'Crown and bridge',
    color: '#1ca11a',
  },
  {
    id: 'job7',
    start: '2026-10-22T17:00',
    end: '2026-10-22T18:00',
    resource: 4,
    title: 'Harley Thomson',
    job: 'Tartar removal',
    color: '#a446b5',
  },
  {
    id: 'job8',
    start: '2026-10-22T09:00',
    end: '2026-10-22T11:00',
    resource: 6,
    title: 'Ricky Welch',
    job: 'Wisdom tooth removal',
    color: '#334ab9',
  },
];

for (var i = 0; i < myData.length; ++i) {
  var event = myData[i];
  // Convert dates to date objects
  event.start = event.start ? new Date(event.start) : event.start;
  event.end = event.end ? new Date(event.end) : event.end;
  // Mark past events as fixed by setting the event.editable property to false
  event.editable = event.start && today < event.start;
}

var myCalendar = mobiscroll.eventcalendar('#md-docs-appointment-calendar', {
  view: {
    scheduler: {
      type: 'day',
      startTime: '08:00',
      endTime: '20:00',
      allDay: false,
    },
  },
  data: myData,
  resources: [
    {
      id: 1,
      name: 'Dr. Keila Delores',
    },
    {
      id: 2,
      name: 'Dr. Gene Cortez',
    },
    {
      id: 3,
      name: 'Dr. Paula Bush',
    },
    {
      id: 4,
      name: 'Dr. Pete Nichols',
    },
    {
      id: 5,
      name: 'Dr. Jean Pearson',
    },
    {
      id: 6,
      name: 'Dr. Thelma Cain',
    },
  ],
  invalid: [
    {
      recurring: {
        repeat: 'daily',
        until: yesterday,
      },
    },
    {
      start: yesterday,
      end: today,
    },
  ],
  dragToMove: true,
  dragToCreate: true,
  eventOverlap: false,
  externalDrop: true,
  externalDrag: true,
  extendDefaultEvent: function () {
    return {
      job: 'Tartar removal',
      color: '#a446b5',
    };
  },
  onEventCreate: function (args) {
    var event = args.event;
    event.unscheduled = false;
    myCalendar.setOptions({
      colors: [],
    });
  },
  onEventCreated: function (args) {
    mobiscroll.toast({
      message: args.event.title + ' added',
    });
    var elm = document.getElementById('md-event-' + args.event.id);
    if (elm) {
      elm.remove();
    }
  },
  onEventCreateFailed: function (args) {
    if (!(today < args.event.start)) {
      mobiscroll.toast({
        message: "Can't add event in the past",
      });
    } else {
      mobiscroll.toast({
        message: 'Make sure not to double book',
      });
    }
  },
  onEventUpdateFailed: function () {
    mobiscroll.toast({
      message: 'Make sure not to double book',
    });
  },
  onEventDelete: function (args) {
    mobiscroll.toast({
      message: args.event.title + ' unscheduled',
    });
  },
  onEventDragEnter: function () {
    myCalendar.setOptions({
      colors: [
        {
          background: '#f1fff24d',
          start: '08:00',
          end: '20:00',
          recurring: {
            repeat: 'daily',
          },
        },
      ],
    });
  },
  onEventDragLeave: function () {
    myCalendar.setOptions({
      colors: [],
    });
  },
});

mobiscroll.dropcontainer('#md-docs-appointment-cont', {
  onItemDrop: function (args) {
    if (args.data) {
      var event = args.data;
      var eventLength = Math.abs(new Date(event.end) - new Date(event.start)) / (60 * 60 * 1000);
      var elm = document.createElement('div');

      elm.setAttribute('id', 'md-event-' + event.id);
      elm.classList.add('docs-appointment-task');
      elm.style.background = event.color;
      elm.innerHTML =
        '<div>' + event.title + ' - ' + event.job + '</div><div>' + eventLength + ' hour' + (eventLength > 1 ? 's' : '') + '</div>';

      event.unscheduled = true;

      document.getElementById('md-docs-tasks').appendChild(elm);

      mobiscroll.draggable('#md-event-' + event.id, {
        dragData: event,
      });
    }
    externalCont.style.backgroundColor = '';
  },
  onItemDragEnter: function (args) {
    if (!(args.data && args.data.unscheduled)) {
      externalCont.style.backgroundColor = '#d0e7d2cc';
    }
  },
  onItemDragLeave: function () {
    externalCont.style.backgroundColor = '';
  },
});
```

### HTML

```html
<div class="mbsc-grid mbsc-no-padding">
    <div class="mbsc-row">
        <div class="mbsc-col-sm-9 docs-appointment-calendar">
            <div id="md-docs-appointment-calendar"></div>
        </div>
        <div id="md-docs-appointment-cont" class="mbsc-col-sm-3 docs-appointment-cont">
            <div class="mbsc-form-group-title">Unscheduled appointments</div>
            <div id="md-docs-tasks">
                <div id="md-event-1" mbsc-draggable data-drag-data='{"id":"1","title":"Winfred Lesley","job":"Teeth whitening","start":"08:00","end":"09:30","color":"#d1891f","unscheduled":"true"}' class="docs-appointment-task" style="background: #d1891f;">
                    <div>Winfred Lesley - Teeth whitening</div>
                    <div>1.5 hours</div>
                </div>
                <div id="md-event-2" mbsc-draggable data-drag-data='{"id":"2","title":"Rosalin Delice","job":"Crown and bridge","start":"08:00","end":"10:00","color":"#1ca11a","unscheduled":"true"}' class="docs-appointment-task" style="background: #1ca11a;">
                    <div>Rosalin Delice - Crown and bridge</div>
                    <div>2 hours</div>
                </div>
                <div id="md-event-3" mbsc-draggable data-drag-data='{"id":"3","title":"Macy Steven","job":"Root canal treatment","start":"10:00","end":"12:30","color":"#cb3939","unscheduled":"true"}' class="docs-appointment-task" style="background: #cb3939;">
                    <div>Macy Steven - Root canal treatment</div>
                    <div>2.5 hours</div>
                </div>
                <div id="md-event-4" mbsc-draggable data-drag-data='{"id":"4","title":"Lavern Cameron","job":"Tartar removal","start":"12:00","end":"13:00","color":"#a446b5","unscheduled":"true"}' class="docs-appointment-task" style="background: #a446b5;">
                    <div>Lavern Cameron - Tartar removal</div>
                    <div>1 hour</div>
                </div>
            </div>
        </div>
    </div>
</div>
```

### CSS

```css
.docs-appointment-calendar {
    border-right: 1px solid #ccc;
}

.docs-appointment-calendar .mbsc-readonly-event {
    opacity: .6;
}

.docs-appointment-cont {
    height: 100%;
    overflow: auto;
}

.docs-appointment-task {
    color: #fff;
    background: #999;
    padding: 10px;
    margin: 20px;
    border-radius: 8px;
    font-family: -apple-system, Segoe UI, Roboto, sans-serif;
}

.demo-doctors-appointment.demo-wrapper,
.demo-doctors-appointment .mbsc-grid,
.demo-doctors-appointment .mbsc-row,
.demo-doctors-appointment .docs-appointment-calendar {
    height: 100%;
}
```

## Implementation notes

- Use `type: 'day'` with `startTime: '08:00'`, `endTime: '20:00'`, and `allDay: false`. Define resources as a flat list of doctors, each with an `id` and `name`.
- Block past time with two `invalid` entries: a `recurring: { repeat: 'daily', until: yesterday }` entry for all days before today, and a `{ start: yesterday, end: today }` entry where `today` is the current `Date` with minutes set to `59` so the current hour is fully blocked. On mount, also set `editable: false` on every scheduled event whose `start` is before `today` to lock past appointments in place.
- Set `dragToMove`, `dragToCreate`, `eventOverlap={false}`, `externalDrop`, and `externalDrag` on the `Eventcalendar` to enable full bidirectional drag between the calendar and the external sidebar.
- Render each unscheduled appointment as a component that uses a ref-callback to obtain its DOM element and mounts a `Draggable` with `dragData={event}` and `element={elem}`. Conditionally render the card only when `!event.hide` - Mobiscroll sets `hide: true` during drag to prevent the source card and the drag ghost from both showing.
- Wrap the unscheduled appointments sidebar in a `Dropcontainer` connected via a ref-callback on the container div. In `onItemDrop`, set `unscheduled: true` on `args.data` and push it into the appointments list. In `onItemDragEnter`, apply a green background to the container only when `args.data.unscheduled` is false (calendar event being dragged back). Clear the background in `onItemDragLeave`.
- In `onEventCreate`, set `event.unscheduled = false` directly on the event object and clear the `colors` array. In `onEventCreated`, add the event to the scheduled list, remove it from the appointments list by ID, and show a `Toast`.
- In `onEventDragEnter` (fires when an external draggable enters the calendar), set a `colors` entry with a green tint spanning `'08:00'–'20:00'` with `recurring: { repeat: 'daily' }` to highlight valid drop targets. Clear it in `onEventDragLeave`.
- In `onEventCreateFailed` and `onEventUpdateFailed`, show a `Toast` whose text depends on whether `event.start <= today` ("Can't add event in the past") or not ("Make sure not to double book"). In `onEventDelete`, show an "unscheduled" `Toast` and remove the event from the scheduled list.

## 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: [Work Week](https://demo.mobiscroll.com/javascript/scheduler/work-week-hours.md) - Configure the Javascript Scheduler with blocked break times — set up a business-hours scheduler in one view.
- Next: [Doctors Availability](https://demo.mobiscroll.com/javascript/scheduler/doctors-availability-calendar-planner.md) - Display per-doctor availability in Javascript Scheduler — color-coded tracks mark open slots per doctor, automatically respecting working hours.
