---
title: "Timeline Dual Synchronized Flight Scheduling Example | Mobiscroll"
component: "Timeline"
framework: "JavaScript"
library_version: "6.2.0"
url: "https://demo.mobiscroll.com/timeline/flight-scheduling-two-synchronized-timelines"
---

# Dual Synchronized Flight Scheduling - JavaScript Timeline demo

Use synchronized JavaScript Timeline views to build a flight scheduling UI — drag flights to assign them to aircraft, with fixed event times and conflict detection.

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

## Description

For creating complex dashboards like for managing jet bookings sometimes two separate timeline instances are necessary.

Sticking two instances together with synchronized horizontal scrolling allows for a setup where the top timeline is a list of bookings with one flight per rows and the bottom timeline holds available aircrafts.

Only moving events from the top timeline is allowed and events are fixed in time, meaning the start and end cannot be changed. Things like `eventOverlap` is set to `false` to avoid overlapping events.

## What this demo shows

- Two stacked daily timeline views are synchronized horizontally so flights can be planned across two resource types on the same 24-hour time scale.
- **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 days and jump back to the current day.
- **Date strip** The strip below the header shows the selected day using the `DD DDD MMM YYYY` date format, with the current date highlighted.
- **Time grid** The timeline uses hourly columns across a 24-hour day.
- **Top timeline** The first timeline shows flight events against pilot resources.
- **Bottom timeline** The second timeline hides its own day and hour header while keeping the aircraft resources visible.
- **Aircraft resources** The bottom timeline uses expandable and collapsible resource groups with aircraft resources inside each group.
- **Custom resource rendering** Aircraft rows show an image, aircraft name, aircraft number, and a passenger-capacity indicator in the bottom-right corner.
- **Event cards** Events are rendered as blue cards with a left accent stripe, the flight route in bold, and the exact start and end time below the title.
- **Event positioning** Events are positioned by assigned resource and by start and end time.
- **Event movement** Events keep their original time range but can be moved between resources.
- **Cross-timeline drag and drop** Flights can be dragged from the first timeline to the second timeline to assign them to aircraft.
- **Scheduled state** After a flight is dropped onto the aircraft timeline, the event changes to green and a confirmation toast appears at the bottom center.
- **Drop restrictions** Scheduled flights cannot be dragged back to the first timeline.
- **Overlap prevention** Flights cannot overlap on the same aircraft resource during the same time period.
- **Conflict feedback** When a conflicting drop is attempted, the affected resource row shows a striped background and a toast message indicates the conflict.
- **Event interaction** Events are highlighted on hover.
- **Event selection** Clicking an event selects and highlights it.
- **Vertical scrolling** The resource area scrolls vertically so more resources can be displayed within each timeline.

## Best for

- **Airline operations** Planning flights across crew and aircraft resources on a shared time scale.
- **Flight scheduling** Assigning scheduled flights to available aircraft without changing their time range.
- **Crew management** Reviewing pilot-related flight assignments alongside aircraft availability.
- **Fleet planning** Managing aircraft availability, capacity, and scheduling conflicts in a timeline layout.
- **Transportation scheduling** Coordinating different resource types where assignments need to move between planning stages.
- **Conflict-aware planning** Preventing overlapping assignments on the same resource while giving immediate visual feedback.

## 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
- [`dragToMove`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-dragToMove) - option
- [`dragToCreate`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-dragToCreate) - option
- [`dragToResize`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-dragToResize) - option
- [`eventDelete`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-eventDelete) - option
- [`externalDrag`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-externalDrag) - option
- [`data`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-data) - option
- [`resources`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-resources) - option
- [`onPageLoading`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onPageLoading) - event
- [`navigate`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-navigate) - method
- [`onPageLoaded`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onPageLoaded) - event
- [`onDestroy`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onDestroy) - event
- [`showControls`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-showControls) - option
- [`dragInTime`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-dragInTime) - option
- [`externalDrop`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-externalDrop) - option
- [`eventOverlap`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-eventOverlap) - option
- [`renderResource`](https://mobiscroll.com/docs/javascript/eventcalendar/api#renderer-renderResource) - renderer
- [`onEventCreated`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventCreated) - event
- [`display`](https://mobiscroll.com/docs/javascript/notifications/toast#opt-display) - option
- [`onEventDeleted`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventDeleted) - event
- [`onEventCreateFailed`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventCreateFailed) - event
- [`onEventUpdateFailed`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventUpdateFailed) - event
- [`onEventDragStart`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventDragStart) - event
- [`cssClass`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-cssClass) - option
- [`setOptions`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-setOptions) - method
- [`invalid`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-invalid) - option
- [`onEventDragEnd`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventDragEnd) - event

## Code

### JS

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

function handleFirstScroll(ev) {
  if (secondCalCont && !skipSecondScroll) {
    skipFirstScroll = true;
    secondCalCont.scrollLeft = ev.target.scrollLeft;
  }
  skipFirstScroll = false;
}

function handleSecondScroll(ev) {
  if (firstCalCont && !skipFirstScroll) {
    skipSecondScroll = true;
    firstCalCont.scrollLeft = ev.target.scrollLeft;
  }
  skipSecondScroll = false;
}

var bookings = [
  {
    start: '2026-11-04T06:00',
    end: '2026-11-04T10:00',
    title: 'Budapest - Ljubljana',
    resource: 1,
  },
  {
    start: '2026-11-04T15:00',
    end: '2026-11-04T18:00',
    title: 'Ljubljana - Berlin',
    resource: 1,
  },
  {
    start: '2026-11-04T04:00',
    end: '2026-11-04T12:00',
    title: 'Los Angeles - Dublin',
    resource: 2,
  },
  {
    start: '2026-11-04T18:00',
    end: '2026-11-04T23:00',
    title: 'Dublin - Bucharest',
    resource: 2,
  },
  {
    start: '2026-11-04T06:00',
    end: '2026-11-04T14:00',
    title: 'Chicago - Amsterdam',
    resource: 3,
  },
  {
    start: '2026-11-04T17:00',
    end: '2026-11-04T22:00',
    title: 'Amsterdam - Cairo',
    resource: 3,
  },
  {
    start: '2026-11-04T10:00',
    end: '2026-11-04T14:00',
    title: 'Hong Kong - Sydney',
    resource: 4,
  },
  {
    start: '2026-11-04T15:00',
    end: '2026-11-04T21:00',
    title: 'Sydney - Tokyo',
    resource: 4,
  },
  {
    start: '2026-11-04T04:00',
    end: '2026-11-04T12:00',
    title: 'Paris - Washington, D.C.',
    resource: 5,
  },
  {
    start: '2026-11-04T12:00',
    end: '2026-11-04T18:00',
    title: 'Washington, D.C. - Los-Angeles',
    resource: 5,
  },
  {
    start: '2026-11-04T03:00',
    end: '2026-11-04T11:00',
    title: 'Los Angeles - Dublin',
    resource: 6,
  },
  {
    start: '2026-11-04T13:00',
    end: '2026-11-04T18:00',
    title: 'Dublin - Rome',
    resource: 6,
  },
  {
    start: '2026-11-04T05:00',
    end: '2026-11-04T12:00',
    title: 'Barcelona - Dubai',
    resource: 7,
  },
  {
    start: '2026-11-04T13:00',
    end: '2026-11-04T20:00',
    title: 'Dubai - Tokyo',
    resource: 7,
  },
  {
    start: '2026-11-04T03:30',
    end: '2026-11-04T13:00',
    title: 'Rome - Toronto',
    resource: 8,
  },
  {
    start: '2026-11-04T16:00',
    end: '2026-11-04T19:00',
    title: 'Toronto - New-York',
    resource: 8,
  },
  {
    start: '2026-11-04T03:00',
    end: '2026-11-04T14:00',
    title: 'Vienna - Shanghai',
    resource: 9,
  },
  {
    start: '2026-11-04T16:00',
    end: '2026-11-04T23:30',
    title: 'Shanghai - Moscow',
    resource: 9,
  },
  {
    start: '2026-11-04T08:00',
    end: '2026-11-04T13:00',
    title: 'London - Cairo',
    resource: 10,
  },
  {
    start: '2026-11-04T15:30',
    end: '2026-11-04T19:30',
    title: 'Cairo - Sofia',
    resource: 10,
  },
  {
    start: '2026-11-04T02:00',
    end: '2026-11-04T13:00',
    title: 'Istanbul - New York',
    resource: 11,
  },
  {
    start: '2026-11-04T16:00',
    end: '2026-11-04T20:00',
    title: 'New York - Montreal',
    resource: 11,
  },
  {
    start: '2026-11-04T05:00',
    end: '2026-11-04T11:00',
    title: 'Seattle - Miami',
    resource: 12,
  },
  {
    start: '2026-11-04T12:00',
    end: '2026-11-04T21:00',
    title: 'Miami - Buenos-Aires',
    resource: 12,
  },
  {
    start: '2026-11-05T04:00',
    end: '2026-11-05T16:00',
    title: 'Hong Kong - Sydney',
    resource: 1,
  },
  {
    start: '2026-11-05T06:00',
    end: '2026-11-05T10:00',
    title: 'Los Angeles - Dublin',
    resource: 2,
  },
  {
    start: '2026-11-05T03:00',
    end: '2026-11-05T11:00',
    title: 'Budapest - Ljubljana',
    resource: 4,
  },
  {
    start: '2026-11-05T05:00',
    end: '2026-11-05T12:00',
    title: 'Los Angeles - Dublin',
    resource: 6,
  },
  {
    start: '2026-11-05T13:00',
    end: '2026-11-05T23:00',
    title: 'London - Cairo',
    resource: 7,
  },
  {
    start: '2026-11-05T03:00',
    end: '2026-11-05T14:00',
    title: 'Miami - Buenos-Aires',
    resource: 9,
  },
  {
    start: '2026-11-05T06:00',
    end: '2026-11-05T12:00',
    title: 'Barcelona - Dubai',
    resource: 10,
  },
  {
    start: '2026-11-05T08:00',
    end: '2026-11-05T22:00',
    title: 'Istanbul - New York',
    resource: 11,
  },
  {
    start: '2026-11-05T05:00',
    end: '2026-11-05T11:00',
    title: 'Vienna - Shanghai',
    resource: 12,
  },
];

var reservations = [
  {
    id: 1,
    name: 'Alison Reyes',
  },
  {
    id: 2,
    name: 'Shauna Perry',
  },
  {
    id: 3,
    name: 'Jan Whitney',
  },
  {
    id: 4,
    name: 'Freddie Durham',
  },
  {
    id: 5,
    name: 'William Dillon',
  },
  {
    id: 6,
    name: 'Tyrell Edwards',
  },
  {
    id: 7,
    name: 'Caitlyn Riddle',
  },
  {
    id: 8,
    name: 'Liam Mays',
  },
  {
    id: 9,
    name: 'Frank Medina',
  },
  {
    id: 10,
    name: 'Calvin Larsen',
  },
  {
    id: 11,
    name: 'Heather Walsh',
  },
  {
    id: 12,
    name: 'Conner Paul',
  },
];

var jets = [
  {
    id: 'Op 1',
    name: 'Prestige Air',
    eventCreation: false,
    children: [
      {
        id: 1,
        code: '#AF 7703',
        crew: 52,
        name: 'Red Bolt',
        color: '#1dab2f',
        img: 'https://img.mobiscroll.com/demos/jet-1.jpg',
      },
      {
        id: 2,
        code: '#BQ 4718',
        crew: 47,
        name: 'Skyroar',
        color: '#1dab2f',
        img: 'https://img.mobiscroll.com/demos/jet-2.jpg',
      },
      {
        id: 3,
        code: '#ZM 8430',
        crew: 24,
        name: 'Agile Raven',
        color: '#1dab2f',
        img: 'https://img.mobiscroll.com/demos/jet-3.jpg',
      },
    ],
  },
  {
    id: 'Op 2',
    name: 'Elite Wings',
    eventCreation: false,
    children: [
      {
        id: 4,
        code: '#XG 5500',
        crew: 43,
        name: 'Monsterbolt',
        color: '#1dab2f',
        img: 'https://img.mobiscroll.com/demos/jet-4.jpg',
      },
      {
        id: 5,
        code: '#FL 2531',
        crew: 22,
        name: 'Airrise',
        color: '#1dab2f',
        img: 'https://img.mobiscroll.com/demos/jet-5.jpg',
      },
      {
        id: 6,
        code: '#PA 6487',
        crew: 84,
        name: 'Starblast',
        color: '#1dab2f',
        img: 'https://img.mobiscroll.com/demos/jet-6.jpg',
      },
      {
        id: 7,
        code: '#PP 9812',
        crew: 28,
        name: 'Ebonfire',
        color: '#1dab2f',
        img: 'https://img.mobiscroll.com/demos/jet-7.jpg',
      },
    ],
  },
  {
    id: 'Op 3',
    name: 'Luxury Skies',
    eventCreation: false,
    children: [
      {
        id: 8,
        code: '#DN 3191',
        crew: 36,
        name: 'Dark Bee',
        color: '#1dab2f',
        img: 'https://img.mobiscroll.com/demos/jet-8.jpg',
      },
      {
        id: 9,
        code: '#ZW 2328',
        crew: 76,
        name: 'Keen Sparrow',
        color: '#1dab2f',
        img: 'https://img.mobiscroll.com/demos/jet-9.jpg',
      },
      {
        id: 10,
        code: '#RX 9898',
        crew: 37,
        name: 'Jagged Bolt',
        color: '#1dab2f',
        img: 'https://img.mobiscroll.com/demos/jet-10.jpg',
      },
    ],
  },
];

var flights = [
  {
    start: '2026-11-04T10:00',
    end: '2026-11-04T17:00',
    title: 'Barcelona - Dubai',
    resource: 4,
  },
  {
    start: '2026-11-04T07:30',
    end: '2026-11-04T16:00',
    title: 'Toronto - Rome',
    resource: 8,
  },
  {
    start: '2026-11-04T04:00',
    end: '2026-11-04T08:00',
    title: 'Ljubljana - Budapest',
    resource: 1,
  },
  {
    start: '2026-11-04T02:00',
    end: '2026-11-04T10:00',
    title: 'Paris - Washington, D.C.',
    resource: 2,
  },
  {
    start: '2026-11-04T12:00',
    end: '2026-11-04T23:00',
    title: 'New York - Istanbul',
    resource: 7,
  },
  {
    start: '2026-11-04T15:00',
    end: '2026-11-04T23:00',
    title: 'Moscow - Tokyo',
    resource: 3,
  },
];

var firstDay;
var firstCalCont;
var secondCalCont;
var skipFirstScroll;
var skipSecondScroll;

mobiscroll.eventcalendar('#demo-drag-drop-bw-inst-first', {
  view: {
    timeline: {
      type: 'day',
      size: 1,
    },
  },
  dragToMove: false,
  dragToCreate: false,
  dragToResize: false,
  eventDelete: true,
  externalDrag: true,
  data: bookings,
  resources: reservations,
  onPageLoading: function (args) {
    firstDay = args.firstDay;
    if (secondCalendar) {
      secondCalendar.navigate(firstDay);
    }
  },
  onPageLoaded: function () {
    if (!firstCalCont) {
      firstCalCont = document.querySelector('.md-drag-drop-bw-inst-first .mbsc-timeline-grid-scroll');
      firstCalCont.addEventListener('scroll', handleFirstScroll);
    }
  },
  onDestroy: function () {
    if (firstCalCont) {
      firstCalCont.removeEventListener('scroll', handleFirstScroll);
    }
  },
});

var secondCalendar = mobiscroll.eventcalendar('#demo-drag-drop-bw-inst-second', {
  view: {
    timeline: {
      type: 'day',
      size: 1,
    },
  },
  data: flights,
  showControls: false,
  dragToCreate: false,
  dragToMove: true,
  dragToResize: false,
  dragInTime: false,
  externalDrop: true,
  eventDelete: true,
  eventOverlap: false,
  resources: jets,
  renderResource: function (resource) {
    if (!resource.children) {
      return (
        '<div class="mbsc-flex mbsc-align-items-center mbsc-justify-content-start">' +
        '<img class="md-drag-drop-bw-inst-avatar" src="' +
        resource.img +
        '"/>' +
        '<div class="md-drag-drop-bw-inst-details mbsc-flex-col mbsc-flex-1-0">' +
        '<div>' +
        resource.name +
        '</div>' +
        '<div class="md-aircraft-code mbsc-flex mbsc-justify-content-between">' +
        '<div>' +
        resource.code +
        '</div>' +
        '<div>' +
        '👨🏾‍✈' +
        resource.crew +
        '</div>' +
        '</div>' +
        '</div>' +
        '</div>'
      );
    }
    return '<div>' + resource.name + '</div>';
  },
  onEventCreated: function (args) {
    flights.push(args.event);
    mobiscroll.toast({
      display: 'center',
      message: 'Flight scheduled',
    });
  },
  onEventDeleted: function (args) {
    flights = flights.filter(function (flight) {
      return flight.id !== args.event.id;
    });
  },
  onEventCreateFailed: function () {
    mobiscroll.toast({
      display: 'center',
      message: "There's already a flight on this date",
    });
  },
  onEventUpdateFailed: function () {
    mobiscroll.toast({
      display: 'center',
      message: "There's already a flight on this date",
    });
  },
  onEventDragStart: function (args) {
    var unavailable = {};
    var invalid = {
      cssClass: 'md-drag-drop-invalid',
      recurring: { repeat: 'daily' },
      resource: [],
    };
    // Filter unavailable jets
    flights.forEach(function (flight) {
      if (
        flight.id !== args.event.id &&
        new Date(flight.start) < new Date(args.event.end) &&
        new Date(flight.end) > new Date(args.event.start)
      ) {
        unavailable[flight.resource] = true;
      }
    });
    flights.forEach(function (flight) {
      if (unavailable[flight.resource]) {
        flight.cssClass = 'md-drag-drop-faded';
      }
    });
    jets.forEach(function (group) {
      group.children.forEach(function (jet) {
        if (unavailable[jet.id]) {
          invalid.resource.push(jet.id);
        }
      });
    });
    secondCalendar.setOptions({ invalid: [invalid] });
  },
  onEventDragEnd: function () {
    flights.forEach(function (flight) {
      delete flight.cssClass;
    });
    secondCalendar.setOptions({ invalid: [] });
  },
  onPageLoaded: function () {
    if (!secondCalCont) {
      secondCalCont = document.querySelector('.md-drag-drop-bw-inst-second .mbsc-timeline-grid-scroll');
      secondCalCont.addEventListener('scroll', handleSecondScroll);
    }
  },
  onDestroy: function () {
    if (secondCalCont) {
      secondCalCont.removeEventListener('scroll', handleSecondScroll);
    }
  },
});
```

### HTML

```html
<div class="mbsc-flex-col md-drag-drop-bw-inst-cont">
    <div id="demo-drag-drop-bw-inst-first" class="md-drag-drop-bw-inst-first"></div>
    <div id="demo-drag-drop-bw-inst-second" class="md-drag-drop-bw-inst-second"></div>
</div>
```

### CSS

```css
.md-drag-drop-bw-inst-cont .mbsc-eventcalendar {
  height: 500px;
}

.md-drag-drop-bw-inst-cont .mbsc-timeline-parent {
  height: 32px;
}

.md-drag-drop-bw-inst-cont .mbsc-timeline-resource {
  padding-left: 5px !important;
}

.md-drag-drop-bw-inst-cont .mbsc-timeline-row-gutter {
  height: 6px;
}

.md-drag-drop-bw-inst-cont .mbsc-timeline-resource-depth-step {
  width: 0;
}

.md-drag-drop-bw-inst-second .mbsc-timeline-header,
.md-drag-drop-bw-inst-second .mbsc-timeline-header-sticky {
  display: none;
}

.md-aircraft-code {
  color: #939995;
}

.md-drag-drop-bw-inst-cont {
  min-height: 500px;
  height: 100%;
}

.md-drag-drop-bw-inst-first {
  border-bottom: 5px solid #ccc;
}

.md-drag-drop-bw-inst-avatar {
  max-height: 55px;
  max-width: 55px;
  border-bottom-right-radius: 4px;
  border-top-right-radius: 4px;
  border-bottom-left-radius: 4px;
  border-top-left-radius: 4px;
}

.md-drag-drop-bw-inst-details {
  margin-left: 16px;
  font-size: 12px;
}

.md-drag-drop-bw-inst-title {
  font-size: 12px;
  margin-top: 5px;
}

.md-drag-drop-faded {
  opacity: 0.3;
}

.md-drag-drop-invalid.mbsc-schedule-invalid {
  background: repeating-linear-gradient(-45deg, #f3f3f3, #f3f3f3 11px, #e5e5e5 11px, #e5e5e5 22px);
}
```

## Implementation notes

- Render two Eventcalendar instances stacked vertically: the top shows passenger bookings (one row per passenger; drag-out and delete only) and the bottom shows jet aircraft grouped by operator.
- Use `type: 'day'` with `size: 1` on both calendars so they show the same single day.
- On the top calendar, set `externalDrag: true` so bookings can be dragged out onto the bottom calendar. Set `eventDelete: true` to allow deleting bookings directly. Disable `dragToMove`, `dragToCreate`, and `dragToResize`.
- On the bottom calendar, set `externalDrop: true` to accept dropped bookings, `dragToMove: true` to allow reassignment, `dragInTime: false` to prevent time changes, and `eventDelete: true`. Disable `dragToCreate` and `dragToResize`. Set `eventOverlap: false` to prevent double-booking a jet.
- Set `showControls: false` on the bottom calendar so only the top calendar shows the navigation header. Bind `selectedDate` on the bottom calendar and update it via `onPageLoading` on the top calendar to keep both on the same date. Wire `onSelectedDateChange` on the bottom calendar to propagate manual navigation back.
- Synchronize horizontal scrolling: in `onPageLoaded` for each calendar, locate the `.mbsc-timeline-grid-scroll` scroll container and attach a scroll listener. Each listener copies `scrollLeft` to the other container. Use two boolean skip-flags to prevent infinite scroll loops. Remove the listeners in `onDestroy`.
- Define the bottom calendar's resources as a two-level tree: operator groups with `eventCreation: false` and individual jet leaf nodes with custom `img`, `code`, and `crew` properties.
- Use `renderResource` (Angular: `resourceTemplate`, Vue: `#resource` slot) on the bottom calendar to show a jet photo, name, tail code, and crew count for leaf nodes, and just the operator name for group nodes.
- Use `onEventDragStart` on the bottom calendar to compute availability: find all scheduled flights that overlap the dragged booking's time window, collect their jet resource IDs, and set them as `invalid` with `recurring: { repeat: 'daily' }`. Also apply a fade CSS class to the overlapping flights to visually grey them out.
- Use `onEventDragEnd` to clear the `invalid` array and remove the fade class from all flights.
- Use `onEventCreated` and `onEventDeleted` to update the scheduled flights state. Use `onEventCreateFailed` and `onEventUpdateFailed` to show a `Toast` when a booking cannot be placed due to a conflict.

## 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: [Task Checklist in Events](https://demo.mobiscroll.com/timeline/check-list-tasks-within-events.md) - Render a task checklist inside each JavaScript Timeline event card — row heights expand as the list grows, and a popup let users add new subtasks.
- Next: [Task Progress Tracking](https://demo.mobiscroll.com/timeline/show-task-progress-on-event.md) - Show task completion progress on the JavaScript Timeline events with an inline hover slider for live updates and a progress field in the add/edit dialog.
