Overview

License

Licensed for testing and evaluation purposes. Please see the license agreement included in the sample project. You can use the source code of the tutorial if you are a licensed user of DayPilot Pro for JavaScript. Buy a license.

Automatic Scheduler Event Groups

html5-javascript-scheduler-group-concurrent-events.png

The JavaScript Scheduler supports automatic grouping of concurrent events. This feature helps with compacting the view if there are many events. The groups can be expanded on click.

The grouping is fully automatic and all events that are part of an overlapping block of events will be included.

If you want to create custom groups you can use the approach described in this tutorial.

Defining a Group of Related Events

Let's say we have two events that we want to display as a single group:

javascript-scheduler-expandable-event-groups-basic-event-data.png

dp.events.list = [
  {
    id: 1,
    start: "2019-11-02T00:00:00",
    end: "2019-11-06T00:00:00",
    text: "Event 1 (Group 1)",
    resource: "R2"
  },
  {
    id: 2,
    start: "2019-11-05T00:00:00",
    end: "2019-11-10T00:00:00",
    text: "Event 2 (Group 1)",
    resource: "R2"
  }
];

In order to display them as one event, we need to replace these two records with a single one that defines the group. We'll also add properties that define the boundaries (start, end), name (text) and custom duration bar color to indicate a group (barColor).

The group members will be stored in a members property. This is a custom property name and you can change it as you wish (just make sure it doesn't conflict with the predefined properties).

javascript-scheduler-expandable-event-groups-basic-group.png

dp.events.list = [
  {
    id: 101,
    start: "2019-11-02T00:00:00",
    end: "2019-11-10T00:00:00",
    text: "Group 1",
    resource: "R2",
    barColor: "#00cc00",
    members: [
      {
        id: 1,
        start: "2019-11-02T00:00:00",
        end: "2019-11-06T00:00:00",
        text: "Event 1 (Group 1)",
        group: 101,
        resource: "R2"
      },
      {
        id: 2,
        start: "2019-11-05T00:00:00",
        end: "2019-11-10T00:00:00",
        text: "Event 2 (Group 1)",
        group: 101,
        resource: "R2"
      }
    ]
  },
];

Expand Icon [+]

javascript-scheduler-expandable-event-groups-expand-icon.png

In order to add an expand icon to the groups, we will use onBeforeEventRender event to add an active area to the event.

First, we need to check if the event is a group (it has args.data.members property). Then we add an active area (positioned using right, top, width and height properties) with an SVG icon (image property).

The onClick event handler defines the click action, which is to hide the group and show the members.

dp.onBeforeEventRender = function(args) {
  var areas = args.data.areas || [];

  // this is a group event, add an expand icon
  if (args.data.members) {
    areas.push({
      right: 6,
      top: 10,
      height: 17,
      width: 17,
      image: "plus-17.svg",
      onClick: function(args) {
        var e = args.source;
        var members = e.data.members;

        // hide the group event
        e.data.hidden = true;
        e.data.expanded = true;
        dp.events.update(e);

        // show members
        members.forEach(function(data) {
          dp.events.add(new DayPilot.Event(data));
        });
      }
    });
  }
  args.data.areas = areas;
};

Collapse Icon [-]

javascript-scheduler-expandable-event-groups-collapse-icon.png

We will use the same logic to add the collapse icon to the group members.

First, we detect a group member by checking args.data.group property. We add an active area at the same position but this time we use a different SVG icon (minus-17.svg).

The onClick event handler hides the members and shows the group event.

Note that we also store the collapsed/expanded status using a custom group.data.expanded property. The value is not used in this example but you can use in your application to get the group display status.

dp.onBeforeEventRender = function(args) {
  var areas = args.data.areas || [];

  // ...

  // this is a member event, add a collapse icon
  if (args.data.group) {
    areas.push({
      right: 6,
      top: 10,
      height: 17,
      width: 17,
      image: "minus-17.svg",
      onClick: function(args) {
        var e = args.source;

        // hide members
        var members = dp.events.list.filter(function(data) { return data.group === e.data.group; });
        members.forEach(function(data) {
          dp.events.remove(new DayPilot.Event(data));
        });

        // show the group event
        var group = dp.events.find(e.data.group);
        if (group) {
          group.data.hidden = false;
          group.data.expanded = true;
          dp.events.update(group);
        }
      }
    });
  }
  args.data.areas = areas;
};

Full Source Code

This is the full source code of the JavaScript Scheduler event grouping implementation:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>JavaScript Scheduler: Expandable Event Groups</title>

  <!-- ... -->

  <!-- DayPilot library -->
  <script src="js/daypilot/daypilot-all.min.js"></script>
</head>
<body>
<div class="header">
  <h1><a href='https://code.daypilot.org/22806/javascript-scheduler-expandable-event-groups'>JavaScript Scheduler: Expandable Event Groups</a></h1>
  <div><a href="https://javascript.daypilot.org/">DayPilot for JavaScript</a> - HTML5 Calendar/Scheduling Components for JavaScript/Angular/React/Vue</div>
</div>

<div class="main">
  <div id="dp"></div>
  <div class="generated">Generated using <a href="https://builder.daypilot.org/">DayPilot UI Builder</a>.</div>
</div>

<script>
  var dp = new DayPilot.Scheduler("dp", {
    timeHeaders: [{"groupBy": "Month"}, {"groupBy": "Day", "format": "d"}],
    scale: "Day",
    days: 30,
    startDate: "2019-11-01",
    timeRangeSelectedHandling: "Enabled",
    onTimeRangeSelected: function (args) {
      var dp = this;
      DayPilot.Modal.prompt("Create a new event:", "Event 1").then(function (modal) {
        dp.clearSelection();
        if (!modal.result) {
          return;
        }
        dp.events.add(new DayPilot.Event({
          start: args.start,
          end: args.end,
          id: DayPilot.guid(),
          resource: args.resource,
          text: modal.result
        }));
      });
    },
    treeEnabled: true,
  });
  dp.resources = [
    {name: "Resource 1", id: "R1"},
    {name: "Resource 2", id: "R2"},
    {name: "Resource 3", id: "R3"},
    {name: "Resource 4", id: "R4"},
    {name: "Resource 5", id: "R5"},
    {name: "Resource 6", id: "R6"},
    {name: "Resource 7", id: "R7"},
    {name: "Resource 8", id: "R8"},
  ];
  dp.events.list = [
    {
      id: 101,
      start: "2019-11-02T00:00:00",
      end: "2019-11-10T00:00:00",
      text: "Group 1",
      resource: "R2",
      barColor: "#00cc00",
      members: [
        {
          id: 1,
          start: "2019-11-02T00:00:00",
          end: "2019-11-06T00:00:00",
          text: "Event 1 (Group 1)",
          group: 101,
          resource: "R2"
        },
        {
          id: 2,
          start: "2019-11-05T00:00:00",
          end: "2019-11-10T00:00:00",
          text: "Event 2 (Group 1)",
          group: 101,
          resource: "R2"
        }

      ]
    },
    {
      id: 3,
      start: "2019-11-03T00:00:00",
      end: "2019-11-08T00:00:00",
      text: "Event 3",
      resource: "R4"
    }

  ];
  dp.onBeforeEventRender = function(args) {
    var areas = args.data.areas || [];

    // this is a group event, add an expand icon
    if (args.data.members) {
      areas.push({
        right: 6,
        top: 10,
        height: 17,
        width: 17,
        image: "plus-17.svg",
        onClick: function(args) {
          var e = args.source;
          var members = e.data.members;

          // hide the group event
          e.data.hidden = true;
          e.data.expanded = true;
          dp.events.update(e);

          // show members
          members.forEach(function(data) {
            dp.events.add(new DayPilot.Event(data));
          });
        }
      });
    }
    // this is a member event, add a collapse icon
    else if (args.data.group) {
      areas.push({
        right: 6,
        top: 10,
        height: 17,
        width: 17,
        image: "minus-17.svg",
        onClick: function(args) {
          var e = args.source;

          // hide members
          var members = dp.events.list.filter(function(data) { return data.group === e.data.group; });
          members.forEach(function(data) {
            dp.events.remove(new DayPilot.Event(data));
          });

          // show the group event
          var group = dp.events.find(e.data.group);
          if (group) {
            group.data.hidden = false;
            group.data.expanded = true;
            dp.events.update(group);
          }
        }
      });
    }
    args.data.areas = areas;
  };

  dp.init();
</script>

</body>
</html>