Overview

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

  • Includes a trial version of DayPilot Pro for JavaScript (see License below)

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.

Find the Next Free Slot in the Scheduler Row

We add an icon to the JavaScript Scheduler row header (displayed on hover) which will search the row on click. First, we need to create the active area that displays the icon:

args.row.areas = [
  {
    right: 6,
    top: 8,
    height: 16,
    width: 16,
    visibility: "Hover",
    symbol: "svg/daypilot.svg#minichevron-right-2",
    style: "border: 1px solid #ccc; background-color: white; 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: click => {
  const start = dp.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;
    }
  });

  dp.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: 24px;
    }
  </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>
  var dp = new DayPilot.Scheduler("dp", {
    timeHeaders: [{"groupBy": "Month"}, {"groupBy": "Day", "format": "d"}],
    scale: "Day",
    days: 150,
    startDate: new DayPilot.Date("2021-04-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.canceled) {
          return;
        }
        dp.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: 8,
          height: 16,
          width: 16,
          visibility: "Hover",
          symbol: "svg/daypilot.svg#minichevron-right-2",
          style: "border: 1px solid #ccc; background-color: white; border-radius: 3px; cursor: pointer;",
          toolTip: "Find next free slot",
          onClick: click => {
            const start = dp.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;
              }
            });

            dp.scrollTo(pos, "fast");
          }
        }
      ]
    }
  });
  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"},
    {name: "Resource 9", id: "R9"},
  ];
  dp.events.list = [];

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


  dp.init();


</script>

</body>
</html>