Overview

  • Let users scroll quickly to the next free time slot in the JavaScript Scheduler row.

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.

Online Demo

Find the Next Free Slot in the Scheduler Row

We will add an icon to the row headers of the JavaScript Scheduler. The icon will be displayed on hover and it will search the row on click.

First, we need to create the active area that displays the icon:

onBeforeRowHeaderRender: args => {
  args.row.areas = [
    {
      right: 6,
      top: "calc(50% - 8px)",
      height: 16,
      width: 16,
      visibility: "Hover",
      symbol: "/icons/daypilot.svg#minichevron-right-2",
      backColor: "#4caf50",
      fontColor: "#ffffff",
      style: "border-radius: 3px; cursor: pointer;",
      toolTip: "Find next free slot",
    }
  ];
}

The active area onClick handler searches the row events and finds the next free slot.

It gets the events in the row using args.row.events.all() and filters them to only include the future events. Then it skips any current free space (to allow jumping to the next free slot) and finds the last event without a subsequent event. When a free slot is found, it scrolls there using the scrollTo() method.

onClick: () => {
  const start = scheduler.getViewPort().start;

  // search only upcoming events
  const events = args.row.events.all().filter(e => e.start() >= start);
  if (events.length === 0) {
    return;
  }

  // find the first event the current position is at free slot
  let pos = start;
  if (events[0].start() >= pos) {
    pos = events[0].start();
  }

  events.some(e => {
    const isThereEvent = DayPilot.Util.overlaps(e.start(), e.end(), pos, pos.addSeconds(1));
    if (isThereEvent) {
      pos = e.end();
    } else {
      return true;
    }
  });

  scheduler.scrollTo(pos, "fast");
}

Full Source Code

Here is the full source code of the example. It adds the active areas to the row headers using onBeforeRowHeaderRender event handler. It also generates events with random gaps in the “R2” row.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>JavaScript Scheduler: Find Next Free Slot</title>
  <link href="index.css" type="text/css" rel="stylesheet" />

  <style>
    body .scheduler_default_rowheader_inner {
      padding-right: 30px;
    }

    body .scheduler_default_event_inner {
      background: #fcb71199;
      border-radius: 5px;
      border: 1px solid #fcb711;
      color: #333333;
    }
  </style>

  <!-- DayPilot library -->
  <script src="js/daypilot/daypilot-all.min.js"></script>
</head>
<body>
<div class="header">
  <h1><a href='https://code.daypilot.org/94263/javascript-scheduler-find-next-free-slot'>JavaScript Scheduler: Find Next
    Free Slot</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>
  const scheduler = new DayPilot.Scheduler("dp", {
    timeHeaders: [{"groupBy": "Month"}, {"groupBy": "Day", "format": "d"}],
    scale: "Day",
    days: 150,
    cellWidth: 60,
    eventHeight: 60,
    durationBarVisible: false,
    rowMarginBottom: 3,
    rowMarginTop: 3,
    startDate: new DayPilot.Date("2025-04-01"),
    timeRangeSelectedHandling: "Enabled",
    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
      });
    },
    treeEnabled: true,
    onBeforeRowHeaderRender: args => {
      args.row.areas = [
        {
          right: 6,
          top: "calc(50% - 8px)",
          height: 16,
          width: 16,
          visibility: "Hover",
          symbol: "/icons/daypilot.svg#minichevron-right-2",
          backColor: "#4caf50",
          fontColor: "#ffffff",
          style: "border-radius: 3px; cursor: pointer;",
          toolTip: "Find next free slot",
          onClick: () => {
            const start = scheduler.getViewPort().start;

            // search only upcoming events
            const events = args.row.events.all().filter(e => e.start() >= start);
            if (events.length === 0) {
              return;
            }

            // find the first event the current position is at free slot
            let pos = start;
            if (events[0].start() >= pos) {
              pos = events[0].start();
            }

            events.some(e => {
              const isThereEvent = DayPilot.Util.overlaps(e.start(), e.end(), pos, pos.addSeconds(1));
              if (isThereEvent) {
                pos = e.end();
              } else {
                return true;
              }
            });

            scheduler.scrollTo(pos, "fast");
          }
        }
      ];
    }
  });
  scheduler.init();

  const app = {
    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"},
        {name: "Resource 8", id: "R8"},
        {name: "Resource 9", id: "R9"},
      ];

      // add a sequence of events with random spaces
      const events = [];
      for (let i = 0; i < 150; i++) {
        const e = {
          start: scheduler.startDate.addDays(i),
          end: scheduler.startDate.addDays(i + 1),
          id: i,
          resource: "R2",
          text: "Event " + i
        };
        const add = Math.random() > 0.2;
        if (add) {
          events.push(e);
        }
      }

      scheduler.update({resources, events});
    }
  };
  app.loadData();

</script>

</body>
</html>