Features

  • Split Scheduler events at a specified location using a context menu.

  • Optional snap-to-grid mode splits the event at the cell start.

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.

JavaScript Scheduler Config

JavaScript Scheduler Event Splitting - Configuration

This is the starting configuration of HTML5/JavaScript Scheduler component generated using Scheduler UI Builder. In the next steps, we will extend it with the splitting functionality.

<div id="scheduler"></div>

<script>
  const scheduler = new DayPilot.Scheduler("scheduler", {
    timeHeaders: [{"groupBy":"Month"},{"groupBy":"Day","format":"d"}],
    scale: "Day",
    days: DayPilot.Date.today().daysInMonth(),
    startDate: DayPilot.Date.today().firstDayOfMonth(),
    // ...
  });
  scheduler.init();

  const 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"},
  ];
  const events = [
    {
      text: "Event 1",
      start: DayPilot.Date.today().firstDayOfMonth().addDays(1),
      end: DayPilot.Date.today().firstDayOfMonth().addDays(6),
      resource: "R1",
      id: 1
    }
  ];
  scheduler.update({resources, events});

</script>

How to Get the Current Mouse Position in Pixels

The current mouse position in pixels (relative to the Scheduler grid) is accessible using the getCoords() method:

const position = scheduler.getCoords().x;

How to Get the Current Mouse Position as Date/Time

You can use the getDate() method to convert the pixel position to date/time value:

const position = scheduler.getDate(scheduler.getCoords().x, true);

The second parameter specifies whether an exact position should be calculated. If you use false value, it will return the start of the current grid cell.

const position = scheduler.getDate(scheduler.getCoords().x, false);

How to Split Events using Context Menu

JavaScript Scheduler Event Splitting - Context Menu

In this step, we will add a context menu that will let users split an event into two new events at a specified location:

JavaScript Scheduler Event Splitting - Two New Events

The context menu only has a single item ("Split"). It calculates the current date/time position, updates the event with a new end and creates another event starting at this time point:

const scheduler = new DayPilot.Scheduler("scheduler", {
  // ...
  contextMenu: new DayPilot.Menu({
    onShow: (args) => {
      scheduler.contextMenu._x = scheduler.getCoords().x;
    },
    items: [
      {
        text: "Split",
        onClick: (args) => {
          const time = scheduler.getDate(scheduler.contextMenu._x, true);
          const originalEvent = args.source;
          const originalEnd = originalEvent.end();

          originalEvent.data.end = time;
          scheduler.events.update(originalEvent);

          const newEvent = {
            start: time,
            end: originalEnd,
            id: DayPilot.guid(),
            resource: originalEvent.resource(),
            text: originalEvent.text() + ", part 2"
          };

          scheduler.events.add(newEvent);
        }
      }
    ]
  })
});

How to Split Events in Snap-To-Grid Mode

JavaScript Scheduler Event Splitting - Context Menu with Snap-To-Grid Enabled

We will also add a "Snap-to-grid" checkbox which will activate the snap-to-grid mode of the Scheduler. When this option is checked, the event will be split at the start of the current grid cell:

JavaScript Scheduler Event Splitting - Separate Events with Snap-To-Grid Enabled

To calculate the start of the current cell, we need to modify the getDate() call in the context menu item onClick handler. The second parameter (precise) must be false to get a time position aligned with the grid.

const time = scheduler.getDate(scheduler.contextMenu._x, !app.elements.snap.checked);

Full Source Code

Here is the complete source code of our JavaScript Scheduler example, which includes a context menu for events with an option to split the event into two at the current location.

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

  <link type="text/css" rel="stylesheet" href="css/toolbar.css" />

  <!-- DayPilot library -->
  <script src="js/daypilot/daypilot-all.min.js"></script>
</head>
<body>
<div class="header">
  <h1><a href='https://code.daypilot.org/31275/html5-scheduler-splitting-an-event'>JavaScript Scheduler: Splitting an Event</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 class="toolbar">
    <label><input type="checkbox" id="snap"> Snap-to-grid</label>
  </div>
  <div id="scheduler"></div>
</div>

<script>
  const scheduler = new DayPilot.Scheduler("scheduler", {
    timeHeaders: [{"groupBy":"Month"},{"groupBy":"Day","format":"d"}],
    scale: "Day",
    days: DayPilot.Date.today().daysInMonth(),
    startDate: DayPilot.Date.today().firstDayOfMonth(),
    useEventBoxes: "Never",
    snapToGrid: false,
    onTimeRangeSelected: async (args) =>  {
      const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 1");
      scheduler.clearSelection();
      if (modal.canceled) { return; }
      scheduler.events.add({
        start: args.start,
        end: args.end,
        id: DayPilot.guid(),
        resource: args.resource,
        text: modal.result
      });
    },
    onBeforeEventRender: args => {
      if (args.data.barColor) {
        args.data.barBackColor = DayPilot.ColorUtil.lighter(args.data.barColor, 3);
      }
    },
    contextMenu: new DayPilot.Menu({
      onShow: (args) => {
        scheduler.contextMenu._x = scheduler.getCoords().x;
      },
      items: [
        {
          text: "Split",
          onClick: (args) => {
            const time = scheduler.getDate(scheduler.contextMenu._x, !app.elements.snap.checked);
            const originalEvent = args.source;
            const originalEnd = originalEvent.end();

            originalEvent.data.end = time;
            scheduler.events.update(originalEvent);

            const newEvent = {
              start: time,
              end: originalEnd,
              id: DayPilot.guid(),
              resource: originalEvent.resource(),
              barColor: originalEvent.data.barColor,
              text: originalEvent.text() + ", part 2"
            };

            scheduler.events.add(newEvent);
          }
        }
      ]
    })
  });
  scheduler.init();

  const app = {
    elements: {
      snap: document.getElementById("snap")
    },
    loadData() {
      const 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"},
      ];
      const events = [
        {
          text: "Event 1",
          start: DayPilot.Date.today().firstDayOfMonth().addDays(1),
          end: DayPilot.Date.today().firstDayOfMonth().addDays(6),
          resource: "R2",
          barColor: "#0d8ecf",
          id: 1
        },
        {
          text: "Event 2",
          start: DayPilot.Date.today().firstDayOfMonth().addDays(8),
          end: DayPilot.Date.today().firstDayOfMonth().addDays(12),
          resource: "R3",
          barColor: "#f3c612",
          id: 2
        },
        {
          text: "Event 3",
          start: DayPilot.Date.today().firstDayOfMonth().addDays(2),
          end: DayPilot.Date.today().firstDayOfMonth().addDays(7),
          resource: "R4",
          barColor: "#e74c3c",
          id: 3
        },
        {
          text: "Event 4",
          start: DayPilot.Date.today().firstDayOfMonth().addDays(12),
          end: DayPilot.Date.today().firstDayOfMonth().addDays(17),
          resource: "R5",
          barColor: "#2ecc71",
          id: 4
        },
        {
          text: "Event 5",
          start: DayPilot.Date.today().firstDayOfMonth().addDays(5),
          end: DayPilot.Date.today().firstDayOfMonth().addDays(9),
          resource: "R6",
          barColor: "#34495e",
          id: 5
        }
      ];
      scheduler.update({resources, events});
    },
    addEventHandlers() {
      app.elements.snap.addEventListener("click", (ev) => {
        const enabled = app.elements.snap.checked;
        const useEventBoxes = enabled ? "Always" : "Never";
        const snapToGrid = enabled;
        scheduler.update({useEventBoxes, snapToGrid});
      });
    },
    init() {
      this.addEventHandlers();
      this.loadData();
    }
  };
  app.init();
</script>


</body>
</html>

You can download the complete project using the link at the top of this article.