html5 javascript scheduler group concurrent events

The Scheduler can display a block of concurrent events as a single collapsed group.

The concurrent group expanded status is persisted during update() calls. Because the event set can change during update() the following logic is used: It remembers IDs of all events in groups that were expanded. During update, it checks those IDs for every new group. If the new group contains at least one event that was in an expanded group in the previous state the group will be expanded.

Expanded group:

html5 javascript scheduler group concurrent events expanded

JavaScript

Configuration properties

Events

Row API

The rows.find() method returns a DayPilot.Row object with the following methods:

  • DayPilot.Row.groups.expandAll()

  • DayPilot.Row.groups.collapseAll()

  • DayPilot.Row.groups.collapsed() - returns an array of collapsed groups

  • DayPilot.Row.groups.expanded() - returns an array of expanded groups

The group object has the following methods:

  • expand()

  • collapse()

Example

dp.groupConcurrentEvents = true;
dp.groupConcurrentEventsLimit = 2;  // don't group if there aren't more than 2 overlapping events

dp.onBeforeRowHeaderRender = function (args) {
    var hasExpanded = args.row.groups.expanded().length > 0;
    var hasCollapsed = args.row.groups.collapsed().length > 0;

    if (hasExpanded && hasCollapsed) {
        args.row.areas = [
            {
                v: "Visible",
                right: 14,
                top: 0,
                height: 12,
                width: 12,
                style: "cursor:pointer",
                image: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAIAAABv85FHAAAAKXRFWHRDcmVhdGlvbiBUaW1lAHDhIDMwIEkgMjAwOSAwODo0NjozMSArMDEwMClDkt4AAAAHdElNRQfZAR4HLyUoFBT0AAAACXBIWXMAAA7CAAAOwgEVKEqAAAAABGdBTUEAALGPC/xhBQAAAFJJREFUeNpjrK6s5uTl/P75OybJ0NLW8h8bAIozgeRhgJGREc4GijMBtTNgA0BxFog+uA4IA2gmUJwFog/IgUhAGBB9KPYhA3T74Jog+hjx+A8A1KRQ+AN5vcwAAAAASUVORK5CYII=',
                onClick: function (args) {
                    var row = args.source;
                    row.groups.expandAll();
                }
            },
            {
                v: "Visible",
                right: 0,
                top: 0,
                height: 12,
                width: 12,
                style: "cursor:pointer",
                image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAIAAABv85FHAAAAKXRFWHRDcmVhdGlvbiBUaW1lAHDhIDMwIEkgMjAwOSAwODo0NjozMSArMDEwMClDkt4AAAAHdElNRQfZAR4HLxB+p9DXAAAACXBIWXMAAA7CAAAOwgEVKEqAAAAABGdBTUEAALGPC/xhBQAAAENJREFUeNpjrK6s5uTl/P75OybJ0NLW8h8bAIozgeSxAaA4E1A7VjmgOAtEHyMjI7IE0EygOAtEH5CDqY9c+xjx+A8ANndK9WaZlP4AAAAASUVORK5CYII=",
                onClick: function (args) {
                    var row = args.source;
                    row.groups.collapseAll();
                }
            }
        ];
    } else if (hasCollapsed) {
        args.row.areas = [
            {
                v: "Visible",
                right: 0,
                top: 0,
                height: 12,
                width: 12,
                style: "cursor:pointer",
                image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAIAAABv85FHAAAAKXRFWHRDcmVhdGlvbiBUaW1lAHDhIDMwIEkgMjAwOSAwODo0NjozMSArMDEwMClDkt4AAAAHdElNRQfZAR4HLyUoFBT0AAAACXBIWXMAAA7CAAAOwgEVKEqAAAAABGdBTUEAALGPC/xhBQAAAFJJREFUeNpjrK6s5uTl/P75OybJ0NLW8h8bAIozgeRhgJGREc4GijMBtTNgA0BxFog+uA4IA2gmUJwFog/IgUhAGBB9KPYhA3T74Jog+hjx+A8A1KRQ+AN5vcwAAAAASUVORK5CYII=",
                onClick: function (args) {
                    var row = args.source;
                    row.groups.expandAll();
                }
            },
        ];
    } else if (hasExpanded) {
        args.row.areas = [
            {
                v: "Visible",
                right: 0,
                top: 0,
                height: 12,
                width: 12,
                style: "cursor:pointer",
                image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAIAAABv85FHAAAAKXRFWHRDcmVhdGlvbiBUaW1lAHDhIDMwIEkgMjAwOSAwODo0NjozMSArMDEwMClDkt4AAAAHdElNRQfZAR4HLxB+p9DXAAAACXBIWXMAAA7CAAAOwgEVKEqAAAAABGdBTUEAALGPC/xhBQAAAENJREFUeNpjrK6s5uTl/P75OybJ0NLW8h8bAIozgeSxAaA4E1A7VjmgOAtEHyMjI7IE0EygOAtEH5CDqY9c+xjx+A8ANndK9WaZlP4AAAAASUVORK5CYII=",
                onClick: function (args) {
                    var row = args.source;
                    row.groups.collapseAll();
                }
            }
        ];
    }
};

Demo

API: groupConcurrentEvents.

The examples create five bookings with the same dates in Room A, making the collapsed group easy to see.

The framework examples use DayPilot Pro. For installation, see the Angular, React or Vue Scheduler guide.

Angular

Enable groupConcurrentEvents in the typed config containing the generated event array.

import {Component} from "@angular/core";
import {DayPilot, DayPilotModule} from "daypilot-pro-angular";

@Component({
  selector: "app-scheduler-example",
  standalone: true,
  imports: [DayPilotModule],
  template: `
    
    <daypilot-scheduler [config]="config"></daypilot-scheduler>
  `
})
export class SchedulerExampleComponent {
  config: DayPilot.SchedulerConfig = {
    startDate: "2026-11-01",
    days: 30,
    scale: "Day",
    timeHeaders: [{groupBy: "Month"}, {groupBy: "Day", format: "d"}],
    resources: [{id: "A", name: "Room A"}, {id: "B", name: "Room B"}],
    events: Array.from({length: 5}, (_, i) => ({id: i, text: "Booking " + i, resource: "A", start: "2026-11-03", end: "2026-11-06"})),
    groupConcurrentEvents: true
  };

}

React

Use the Boolean prop groupConcurrentEvents={true} alongside the events prop.

import React from "react";
import {DayPilot, DayPilotScheduler} from "daypilot-pro-react";

export default function SchedulerExample() {

  return <div>

    <DayPilotScheduler
      startDate={"2026-11-01"}
      days={30}
      scale={"Day"}
      timeHeaders={[{groupBy: "Month"}, {groupBy: "Day", format: "d"}]}
      resources={[{id: "A", name: "Room A"}, {id: "B", name: "Room B"}]}
      events={Array.from({length: 5}, (_, i) => ({id: i, text: "Booking " + i, resource: "A", start: "2026-11-03", end: "2026-11-06"}))}
      groupConcurrentEvents={true}
    />
  </div>;
}

Vue

Bind :groupConcurrentEvents to the Boolean declared in the script; :events supplies the overlapping bookings.

<script setup>
import {DayPilot, DayPilotScheduler} from "daypilot-pro-vue";

const startDate = "2026-11-01";
const days = 30;
const scale = "Day";
const timeHeaders = [{groupBy: "Month"}, {groupBy: "Day", format: "d"}];
const resources = [{id: "A", name: "Room A"}, {id: "B", name: "Room B"}];
const events = Array.from({length: 5}, (_, i) => ({id: i, text: "Booking " + i, resource: "A", start: "2026-11-03", end: "2026-11-06"}));
const groupConcurrentEvents = true;

</script>

<template>
  <div>

    <DayPilotScheduler
      :startDate="startDate"
      :days="days"
      :scale="scale"
      :timeHeaders="timeHeaders"
      :resources="resources"
      :events="events"
      :groupConcurrentEvents="groupConcurrentEvents"
    />
  </div>
</template>

ASP.NET MVC

Example

@Html.DayPilotScheduler("dps", new DayPilotSchedulerConfig {
  // ...
  GroupConcurrentEvents = true,
  GroupConcurrentEventsLimit = 2;  // don't group if there aren't more than 2 overlapping events
})

Demo

Availability

Availability of this feature in DayPilot editions:

ProductLitePro
DayPilot for JavaScript
DayPilot for ASP.NET WebForms
DayPilot for ASP.NET MVC
DayPilot for Java