---
title: "Javascript Scheduler Google Calendar Integration Example | Mobiscroll"
component: "Scheduler"
framework: "JavaScript"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/javascript/scheduler/sync-events-google-calendar"
---

# Google Calendar Integration - JavaScript Scheduler demo

Connect the Javascript Scheduler to Google Calendar — authenticate users, load calendars, and sync create, edit, or delete events in real time via the integration plugin. Plain JavaScript API.

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

## Description

The event calendar comes with third party calendar integration support through the [**integration plugin**](https://mobiscroll.com/docs/javascript/eventcalendar/calendar-integrations). This needs to be added to the download package or installed separately from a dedicated NPM package.

It includes everything you need to authenticate your users, get their Google calendars and provides functions for CRUD operations. It takes care of loading the events and converts them to the required format so that they can be displayed on the Mobiscroll calendar separately or mixed with other calendars and events.

Customizing the interaction, event workflows, whether the events are read-only is up to you. In this live demo, newly created events will land in the `primary calendar` of the authenticated user.

This example uses the [**integration plugin**](https://mobiscroll.com/docs/javascript/eventcalendar/calendar-integrations) to connect to and load events from Google Calendar and it is not available in the trial. Give the live demo a try and see how it can be configured.

## What this demo shows

- A desktop weekly scheduler layout with a fixed week strip at the top, a fixed all-day row below it, and a scrollable scheduler time grid for the selected week.
- **Layout** Next to the scheduler a separate panel on the left is displayed for Google authentication, editing settings, and calendar selection.
- **Signed-out state** No events are loaded by default, and the side panel prompts the user to sign in with the text `Log into your Google account to view and edit your Google Calendar events` followed by a `Sign in with Google` button.
- **Authentication flow** Clicking the Google sign-in button opens a new browser tab where the user can authenticate with their Google account.
- **Signed-in state** After sign-in, the side panel changes to an authenticated state with guidance about editing events and syncing changes back to Google Calendar.
- **Editing toggle** The authenticated panel includes an `Enable editing` switch, which is off by default and controls whether event edits are allowed or not.
- **Editing toggle enabled** Editing an event opens a popup with the following message `Are you sure you want to update this event? This actin will affect your Google Calendar event.` and below that ther is a `Cancel` and `Update` button, by clicking the firs one, the popup closes and the event editing won't happen and by clicking the second button will update the event on Mobiscroll scheduler and sync back to the Google Calendar as well. 
- **Event creation or update** If a new event is created or an existing one updated, a `Event created/updated/ deleted in [the name of your Google Calendar] calendar` toast appears at the bottom center of the scheduler.
- **Calendar list** The `My Calendars` section shows multiple calendar switches, such as the user's personal calendar and country-specific holiday calendars.
- **Sign-out** The bottom of the side panel includes a `Log out of my account` button for disconnecting the Google Calendar account.
- **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 weeks and jump back to the current day.
- **Week view** The fixed strip below the header displays the days of the selected week, with the current date highlighted by a blue circle.
- **All-day events** All-day events appear in a dedicated row that stays fixed above the time grid.
- **Time grid** The scheduler grid scrolls vertically through the hours of the selected week.
- **Timed events** Once Google Calendar events are loaded, timed events are displayed in the weekly scheduler grid as colored cards with a colored stripe on the left, the event title in bold, and the exact start and end time displayed above the title.
- **Current time** A blue current-time line appears across the time grid, with a small blue dot marking the current date.
- **Hover behavior** Hovering over the time grid shows a time indicator that follows the cursor in 15-minute increments.
- **Event interaction** Hovering over events highlights them and shows drag and resize handles for repositioning or changing duration.
- **Event selection** Clicking an event selects and highlights it.
- **Event creation** Double-clicking the time grid or clicking and dragging on it creates a new event.

## Best for

- **Personal scheduling interfaces** Building scheduler views where users need to browse and manage their own events from an existing Google Calendar.
- **Calendar overlay experiences** Letting users toggle multiple Google calendars, such as personal and holiday calendars, within the same scheduler.

## 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
- [`setEvents`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-setEvents) - method
- [`view`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-view) - option
- [`extendDefaultEvent`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-extendDefaultEvent) - option
- [`exclusiveEndDates`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-exclusiveEndDates) - option
- [`clickToCreate`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-clickToCreate) - option
- [`dragToCreate`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-dragToCreate) - option
- [`dragToMove`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-dragToMove) - option
- [`dragToResize`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-dragToResize) - option
- [`renderHeader`](https://mobiscroll.com/docs/javascript/eventcalendar/api#renderer-renderHeader) - renderer
- [`onPageLoading`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onPageLoading) - event
- [`onEventCreate`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventCreate) - event
- [`removeEvent`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-removeEvent) - method
- [`addEvent`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-addEvent) - method
- [`onEventUpdate`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventUpdate) - event
- [`updateEvent`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-updateEvent) - method
- [`onEventDelete`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventDelete) - event
- [`setOptions`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-setOptions) - method

## Code

### JS

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

var googleCalendarSync = mobiscroll.googleCalendarSync;

var loginButton = document.getElementById('demo-google-cal-sign-in');
var logoutButton = document.getElementById('demo-google-cal-sign-out');

var calendarList = document.getElementById('demo-google-cal-list');
var loggedOutCont = document.getElementById('demo-logged-out-cont');
var loggedInCont = document.getElementById('demo-logged-in-cont');
var cont = document.getElementById('demo-google-calendar-cont');
var editButton = document.getElementById('demo-google-cal-edit');

var startDate;
var endDate;
var calendarIds = [];
var events = [];
var calendarData = {};
var primaryCalendarId;
var debounce;

function toggleContainers(loggedIn) {
  if (loggedIn) {
    loggedOutCont.style.display = 'none';
    loggedInCont.style.display = 'block';
  } else {
    loggedInCont.style.display = 'none';
    loggedOutCont.style.display = 'block';
  }
}

function loadEvents(checked, calendarId) {
  if (checked) {
    calendarIds.push(calendarId);
  } else {
    var index = calendarIds.indexOf(calendarId);
    if (index !== -1) {
      calendarIds.splice(index, 1);
    }
  }
  if (calendarIds.length === 0) {
    events = [];
    inst.setEvents(events);
    return;
  }
  loadingEvents(true);
  googleCalendarSync
    .getEvents(calendarIds, startDate, endDate)
    .then(function (resp) {
      loadingEvents(false);
      events = resp;
      inst.setEvents(events);
    })
    .catch(onError);
}

function onSignedIn() {
  toggleContainers(true);
  googleCalendarSync
    .getCalendars()
    .then(function (calendars) {
      var calList = '<div class="mbsc-form-group-title">My Calendars</div>';

      calendars.sort(function (c) {
        return c.primary ? -1 : 1;
      });
      primaryCalendarId = calendars[0].id;
      calendarIds.push(primaryCalendarId);

      for (var i = 0; i < calendars.length; ++i) {
        var c = calendars[i];
        calList +=
          '<label>' +
          '<input mbsc-switch id="' +
          i +
          '" value="' +
          c.id +
          '" data-label="' +
          c.summary +
          '" type="checkbox" class="google-calendar-switch"' +
          (i == 0 ? 'checked' : '') +
          ' />' +
          '</label>';

        calendarData[c.id] = { name: c.summary, color: c.backgroundColor };
      }

      calendarList.innerHTML = calList;
      mobiscroll.enhance(calendarList);

      loadingEvents(true);

      googleCalendarSync
        .getEvents([primaryCalendarId], startDate, endDate)
        .then(function (resp) {
          loadingEvents(false);
          events = resp;
          inst.setEvents(events);
        })
        .catch(onError);
    })
    .catch(onError);
}

function onSignedOut() {
  toggleContainers(false);
  calendarIds = [];
  calendarData = [];
  events = [];
  inst.setEvents([]);
  calendarList.innerHTML = '';
}

function onError(resp) {
  mobiscroll.toast({
    message: resp.error ? resp.error : resp.result.error.message,
  });
}

function loadingEvents(show) {
  if (show) {
    cont.classList.add('md-loading-events');
  } else {
    cont.classList.remove('md-loading-events');
  }
}

var inst = mobiscroll.eventcalendar('#demo-google-calendar', {
  view: {
    scheduler: { type: 'week' },
  },
  extendDefaultEvent: function () {
    if (primaryCalendarId) {
      return {
        color: calendarData[primaryCalendarId].color,
      };
    }
  },
  exclusiveEndDates: true,
  clickToCreate: false,
  dragToCreate: false,
  dragToMove: false,
  dragToResize: false,
  renderHeader: function () {
    return (
      '<div mbsc-calendar-nav></div>' +
      '<div class="md-spinner">' +
      '<div class="md-spinner-blade"></div>' +
      '<div class="md-spinner-blade"></div>' +
      '<div class="md-spinner-blade"></div>' +
      '<div class="md-spinner-blade"></div>' +
      '<div class="md-spinner-blade"></div>' +
      '<div class="md-spinner-blade"></div>' +
      '<div class="md-spinner-blade"></div>' +
      '<div class="md-spinner-blade"></div>' +
      '<div class="md-spinner-blade"></div>' +
      '<div class="md-spinner-blade"></div>' +
      '<div class="md-spinner-blade"></div>' +
      '<div class="md-spinner-blade"></div>' +
      '</div>' +
      '<div class="md-google-calendar-header">' +
      '<button mbsc-calendar-prev></button>' +
      '<button mbsc-calendar-today>Today</button>' +
      '<button mbsc-calendar-next></button></div>'
    );
  },
  onPageLoading: function (args) {
    startDate = args.viewStart;
    endDate = args.viewEnd;
    clearTimeout(debounce);
    debounce = setTimeout(function () {
      if (googleCalendarSync.isSignedIn()) {
        loadingEvents(true);
        googleCalendarSync
          .getEvents(calendarIds, startDate, endDate)
          .then(function (resp) {
            loadingEvents(false);
            events = resp;
            inst.setEvents(events);
          })
          .catch(onError);
      }
    }, 200);
  },
  onEventCreate: function (args) {
    if (googleCalendarSync.isSignedIn()) {
      var event = args.event;
      googleCalendarSync
        .addEvent(primaryCalendarId, event)
        .then(function (resp) {
          inst.removeEvent(event);
          resp.color = event.color;
          inst.addEvent(resp);
          events.push(resp);
          mobiscroll.toast({
            message: 'Event created in "' + calendarData[primaryCalendarId].name + '" calendar',
          });
        })
        .catch(function (error) {
          onError(error);
          inst.removeEvent(args.event);
        });
    }
  },
  onEventUpdate: function (args) {
    if (googleCalendarSync.isSignedIn()) {
      mobiscroll
        .confirm({
          title: 'Are you sure you want to update this event?',
          message: 'This action will affect your Google Calendar event.',
          okText: 'Update',
        })
        .then(function (result) {
          if (result) {
            var event = args.event;
            var calendarId = event.googleCalendarId;
            googleCalendarSync
              .updateEvent(calendarId, event)
              .then(function () {
                mobiscroll.toast({
                  message: 'Event updated on "' + calendarData[calendarId].name + '" calendar',
                });
              })
              .catch(function (error) {
                inst.updateEvent(args.oldEvent);
                onError(error);
              });
          } else {
            inst.updateEvent(args.oldEvent);
          }
        });
    }
  },
  onEventDelete: function (args) {
    if (googleCalendarSync.isSignedIn()) {
      mobiscroll
        .confirm({
          title: 'Are you sure you want to delete this event?',
          message: 'This action will remove the event from your Google Calendar as well.',
          okText: 'Delete',
        })
        .then(function (result) {
          if (result) {
            var event = args.event;
            var calendarId = event.googleCalendarId;
            googleCalendarSync
              .deleteEvent(calendarId, event)
              .then(function () {
                inst.removeEvent(event);
                events = events.filter(function (ev) {
                  return event.id !== ev.id;
                });
                mobiscroll.toast({
                  message: 'Event deleted from "' + calendarData[calendarId].name + '" calendar',
                });
              })
              .catch(onError);
          }
        })
        .catch(onError);
    }
    return false;
  },
});

// Sign in
loginButton.addEventListener('click', function () {
  if (!googleCalendarSync.isSignedIn()) {
    googleCalendarSync.signIn().catch(onError);
  }
});

// Switch click
loggedInCont.addEventListener('change', function (ev) {
  if (ev.target.classList.contains('google-calendar-switch')) {
    loadEvents(ev.target.checked, ev.target.value);
  }
});

// Edit click
editButton.addEventListener('change', function (ev) {
  var isEditable = ev.target.checked;
  inst.setOptions({
    clickToCreate: isEditable,
    dragToCreate: isEditable,
    dragToMove: isEditable,
    dragToResize: isEditable,
  });
});

// Sign out
logoutButton.addEventListener('click', function () {
  googleCalendarSync.signOut().catch(onError);
});

// Init google client
googleCalendarSync.init({
  apiKey: '<YOUR_GOOGLE_API_KEY>',
  clientId: '<YOUR_GOOGLE_CLIENT_ID>',
  onSignedIn: onSignedIn,
  onSignedOut: onSignedOut,
});
```

### HTML

```html
<div mbsc-page class="md-sync-events-google-cont">
    <div class="md-sync-events-google-menu">
        <div id="demo-logged-out-cont">
            <div class="mbsc-form-group-inset mbsc-align-center">
                <p class="mbsc-italic mbsc-txt-muted">Log into your Google account to view and edit your Google Calendar events</p>
                <button mbsc-button id="demo-google-cal-sign-in" class="md-sync-events-google-button mbsc-button-block">Sync my google calendars</button>
            </div>
        </div>
        <div id="demo-logged-in-cont" style="display:none">
            <div class="mbsc-form-group-inset mbsc-align-center">
                <p class="mbsc-italic mbsc-txt-muted">Editing events sync back to your calendar when enabled. You'll be asked for confirmation on every action.</p>
            </div>
            <div class="mbsc-form-group-inset">
                <label>
                    <input id="demo-google-cal-edit" mbsc-switch data-label="Enable editing" />
                </label>
            </div>
            <div id="demo-google-cal-list" class="mbsc-form-group-inset md-sync-events-google-inset"></div>
            <div class="mbsc-form-group-inset">
                <button mbsc-button id="demo-google-cal-sign-out" class="md-sync-events-google-button mbsc-button-block">Log out of my account</button>
            </div>
        </div>
    </div>
    <div id="demo-google-calendar-cont" class="md-sync-events-google-calendar">
        <div class="md-sync-events-overlay"></div>
        <div id="demo-google-calendar"></div>
    </div>
</div>
```

### CSS

```css
.md-sync-events-google-cont.mbsc-page {
    display: flex;
    height: 600px;
}

.md-sync-events-google-menu {
    flex: 0 0 350px;
    height: 100%;
    overflow: auto;
}

.md-sync-events-google-calendar {
    flex: 1 1 auto;
    border-left: 1px solid #ccc;
}

.md-google-calendar-header {
    flex: 1 0 auto;
    display: flex;
    justify-content: flex-end;
}

.md-sync-events-google-button {
    padding: 8px 24px 8px 50px;
    line-height: 24px;
    background: url(https://cdn.cdnlogo.com/logos/g/35/google-icon.svg) no-repeat;
    background-size: 18px 18px;
    background-position: 11px 11px;
    background-color: #fff;
    color: #757575;
    font-family: Roboto, arial, sans-serif;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    border-radius: 2px;
    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .25);
    transition: background-color .218s, border-color .218s, box-shadow .218s;
}

.md-sync-events-google-button:active {
    background-color: #eee;
    color: #6d6d6d;
}

.md-sync-events-google-button:hover,
.md-sync-events-google-button:focus {
    box-shadow: 0 0 3px 3px rgb(66, 133, 244, .3);
}

.md-sync-events-google-inset {
    margin-bottom: 0;
}

@media (max-width: 800px) {
    .md-sync-events-google-cont.mbsc-page {
        display: block;
        height: auto;
    }
}

/* loading spinner and overlay */

.md-loading-events .md-sync-events-overlay {
    position: absolute;
    z-index: 2;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    -webkit-transform: translateZ(0);
    transform: translateZ(0);
}

.md-spinner {
    visibility: hidden;
    position: relative;
    width: 20px;
    height: 20px;
}

.md-loading-events .md-spinner {
    visibility: visible;
}

.md-spinner .md-spinner-blade {
    position: absolute;
    left: 44.5%;
    top: 37%;
    width: 10%;
    height: 25%;
    border-radius: 50%/20%;
    background-color: #8C8C8C;
    -webkit-animation: md-spinner-fade 1s linear infinite;
    animation: md-spinner-fade 1s linear infinite;
    -webkit-animation-play-state: paused;
    animation-play-state: paused;
}

.md-spinner .md-spinner-blade:nth-child(1) {
    -webkit-animation-delay: -1.66667s;
    animation-delay: -1.66667s;
    -webkit-transform: rotate(30deg) translate(0, -150%);
    transform: rotate(30deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(2) {
    -webkit-animation-delay: -1.58333s;
    animation-delay: -1.58333s;
    -webkit-transform: rotate(60deg) translate(0, -150%);
    transform: rotate(60deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(3) {
    -webkit-animation-delay: -1.5s;
    animation-delay: -1.5s;
    -webkit-transform: rotate(90deg) translate(0, -150%);
    transform: rotate(90deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(4) {
    -webkit-animation-delay: -1.41667s;
    animation-delay: -1.41667s;
    -webkit-transform: rotate(120deg) translate(0, -150%);
    transform: rotate(120deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(5) {
    -webkit-animation-delay: -1.33333s;
    animation-delay: -1.33333s;
    -webkit-transform: rotate(150deg) translate(0, -150%);
    transform: rotate(150deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(6) {
    -webkit-animation-delay: -1.25s;
    animation-delay: -1.25s;
    -webkit-transform: rotate(180deg) translate(0, -150%);
    transform: rotate(180deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(7) {
    -webkit-animation-delay: -1.16667s;
    animation-delay: -1.16667s;
    -webkit-transform: rotate(210deg) translate(0, -150%);
    transform: rotate(210deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(8) {
    -webkit-animation-delay: -1.08333s;
    animation-delay: -1.08333s;
    -webkit-transform: rotate(240deg) translate(0, -150%);
    transform: rotate(240deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(9) {
    -webkit-animation-delay: -1s;
    animation-delay: -1s;
    -webkit-transform: rotate(270deg) translate(0, -150%);
    transform: rotate(270deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(10) {
    -webkit-animation-delay: -0.91667s;
    animation-delay: -0.91667s;
    -webkit-transform: rotate(300deg) translate(0, -150%);
    transform: rotate(300deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(11) {
    -webkit-animation-delay: -0.83333s;
    animation-delay: -0.83333s;
    -webkit-transform: rotate(330deg) translate(0, -150%);
    transform: rotate(330deg) translate(0, -150%);
}

.md-spinner .md-spinner-blade:nth-child(12) {
    -webkit-animation-delay: -0.75s;
    animation-delay: -0.75s;
    -webkit-transform: rotate(360deg) translate(0, -150%);
    transform: rotate(360deg) translate(0, -150%);
}

.md-loading-events .md-spinner-blade {
    -webkit-animation-play-state: running;
    animation-play-state: running;
}

@-webkit-keyframes md-spinner-fade {
    0% {
        opacity: 0.85;
    }
    50% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.25;
    }
}

@keyframes md-spinner-fade {
    0% {
        opacity: 0.85;
    }
    50% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.25;
    }
}
```

## Implementation notes

- Use `view: { scheduler: { type: 'week' } }` with `exclusiveEndDates: true`. Start with empty `data` - no resources are used; all calendar events appear in a single view.
- Import `googleCalendarSync` from `@mobiscroll/calendar-integration` (a separately installed package). Call `googleCalendarSync.init()` on mount with `apiKey`, `clientId`, `onSignedIn`, and `onSignedOut`.
- **`onSignedIn`**: call `googleCalendarSync.getCalendars()`, sort so the primary calendar appears first, then build a `calendarData` map keyed by `c.id` storing `{ name: c.summary, color: c.backgroundColor, checked }`. Initialize `calendarIds` to `[primaryCalId]` and call `getEvents([primaryCalId], startDate, endDate)` to load the first batch of events. For the imperative API, call `inst.setEvents(events)` in the callback.
- **`onSignedOut`**: clear events, `calendarIds`, `calendarData`, and the calendars list.
- **`onPageLoading`**: debounce by 200ms. If `googleCalendarSync.isSignedIn()`, call `getEvents(calendarIds, args.viewStart, args.viewEnd)` and replace the entire events array. Store `args.viewStart`/`args.viewEnd` in variables - they are also needed by the toggle-calendar handler.
- **Toggle calendar** (per-calendar Switch in sidebar): update `calendarIds` by adding or removing the toggled calendar ID. If the resulting list is empty, clear events immediately and return. Otherwise call `googleCalendarSync.getEvents(calendarIds, startDate, endDate)` with the **full updated list** and replace the entire events array - every toggle reloads all currently-checked calendars together. For the imperative API, call `inst.setEvents(events)`.
- Set `extendDefaultEvent` to return `{ color: calendarData[primaryCalendarId].color }` so newly created events inherit the primary calendar's color.
- **"Enable editing" Switch**: toggles `clickToCreate`, `dragToCreate`, `dragToMove`, and `dragToResize` simultaneously (all off by default). For the imperative API, call `inst.setOptions({ clickToCreate, dragToCreate, dragToMove, dragToResize })` on change.
- **`onEventCreate`**: call `googleCalendarSync.addEvent(primaryCalendarId, event)`. On success, assign `newEvent.color = event.color` and add the server-returned event to the list (for the imperative API, call `inst.removeEvent(event)` then `inst.addEvent(resp)`); show a toast naming the target calendar. On failure, remove the optimistic event and show an error toast.
- **`onEventUpdate`**: show a confirm dialog ("Are you sure you want to update this event?" / "Update"). On confirm, call `googleCalendarSync.updateEvent(event.googleCalendarId, event)` and show a success toast. On cancel or failure, restore `args.oldEvent` (for the imperative API, call `inst.updateEvent(args.oldEvent)`).
- **`onEventDelete`**: return `false` to suppress the default removal. Show a confirm dialog ("Delete"). On confirm, call `googleCalendarSync.deleteEvent(event.googleCalendarId, event)`, remove the event from the list (for the imperative API, call `inst.removeEvent(event)`), and show a toast.
- **Custom header** (`renderHeader`, Angular: `headerTemplate`, Vue: `#header` slot): `CalendarNav` on the left, a loading spinner in the center (visible only while events are loading), then `CalendarPrev`, `CalendarToday`, `CalendarNext` on the right.
- **Layout**: fixed-width sidebar alongside the calendar. Logged-out state: sign-in button. Logged-in state: editing note, "Enable editing" Switch, per-calendar Switches with each calendar's name, "Log out" button.

## Run it in your project

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

- Next: [Outlook Calendar Integration](https://demo.mobiscroll.com/javascript/scheduler/sync-events-outlook-calendar.md) - Connect Outlook Calendar to a Javascript Scheduler — authenticate with Microsoft sign-in, load Outlook events, and sync add, edit, and delete actions.
