Overview

  • How to create custom buttons that navigate to the next event outside of the viewport in JavaScript Scheduler component with sparse data.

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.

Finding the Next Event Outside the Viewport

Find Next Event in JavaScript Scheduler Button

First, we need to get a list of events and sort them by start date:

const events = scheduler.events.all().sort((a, b) => a.start().getTime() - b.start().getTime());

The events.all() method returns an array of DayPilot.Event objects in the original load order. To ensure chronological order, we call .sort() on the array, comparing each event’s start date/time.

To make this reusable, define an eventsSortedByStart() helper:

eventsSortedByStart() {
  return scheduler.events.all().sort((a, b) => a.start().getTime() - b.start().getTime());
},

Now we can focus on the current viewport boundaries.

To get viewport details, use the getViewport() method:

const viewport = scheduler.getViewport();

This returns an object with start and end dates (among other properties). For the next-event logic, we only need the viewport end:

const { end: viewportEnd } = scheduler.getViewport();

The following line finds the first event that starts after the viewport ends:

const nextEvent = this.eventsSortedByStart().find(e => e.start() >= viewportEnd); 

If such an event exists, scroll to it (with animation). Otherwise, show a message:

if (nextEvent) {
  scheduler.scrollTo(nextEvent.start(), true);
} else {
  scheduler.message("No next event found");
}

Finally, bind this logic to a “Next” button click handler:

this.elements.scrollToNext.addEventListener("click", () => {
  const { end: viewportEnd } = scheduler.getViewport();

  const nextEvent = this.eventsSortedByStart()
    .find(e => e.start() >= viewportEnd);          // completely after the viewport

  if (nextEvent) {
    scheduler.scrollTo(nextEvent.start(), app.animatedScrolling);
  } else {
    scheduler.message("No next event found");
  }
});

Finding the Previous Event Outside the Viewport

Find Previous Event in JavaScript Scheduler Button

The “Previous” button follows the same pattern, but we reverse the sorted list and look for events ending before the viewport starts:

  const prevEvent = this.eventsSortedByStart()
    .reverse()
    .find(e => e.end() <= viewportStart);

Bind it to the click handler:

this.elements.scrollToPrev.addEventListener("click", () => {
  const { start: viewportStart } = scheduler.getViewport();

  const prevEvent = this.eventsSortedByStart()
    .reverse()
    .find(e => e.end() <= viewportStart);

  if (prevEvent) {
    scheduler.scrollTo(prevEvent.start(), app.animatedScrolling);
  } else {
    scheduler.message("No previous event found");
  }
});

Full Source Code

Below is the complete HTML and JavaScript example that you can copy and run to see the buttons in action.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>JavaScript Scheduler: Use Custom Button to Scroll to Next Event</title>

  <style>
    button {
      display: inline-block;
      text-align: center;
      background-color: #3c78d8;
      border: 1px solid #1155cc;
      color: #fff;
      padding: 6px 20px;
      cursor: pointer;
      margin-right: 5px;
      width: 140px;
      text-decoration: none;
      border-radius: 5px;
    }
  </style>

  <!-- DayPilot library -->
  <script src="js/daypilot/daypilot-all.min.js"></script>
</head>
<body>
<div class="header">
  <h1><a href='https://code.daypilot.org/94932/javascript-scheduler-button-to-scroll-to-next-event'>JavaScript Scheduler: Use Custom Button to Scroll to Next 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="space">
    Scroll to: <button id="scrollToPrev" class="button">Previous Event</button>
    <button id="scrollToNext" class="button">Next Event</button>
  </div>
  <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: 365,
    startDate: "2025-01-01",
    timeRangeSelectedHandling: "Enabled",
    onTimeRangeSelected: async (args) => {
      const scheduler = args.control;
      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
      });
    },
    eventBorderRadius: 6,
    durationBarVisible: false,
    onBeforeEventRender: args => {
      args.data.backColor = "#f1c232cc";
      args.data.borderColor = "darker";
    },
    treeEnabled: true,
  });
  scheduler.init();

  const app = {
    animatedScrolling: true,
    elements: {
      scrollToPrev: document.getElementById("scrollToPrev"),
      scrollToNext: document.getElementById("scrollToNext")
    },
    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"},
      ];
      // sparse to test scrolling
      const events = [
        {
          start: "2025-01-02T00:00:00",
          end: "2025-01-04T00:00:00",
          id: "1",
          resource: "R2",
          text: "Event 1"
        },
        {
          start: "2025-05-02T00:00:00",
          end: "2025-05-05T00:00:00",
          id: "2",
          resource: "R2",
          text: "Event 2"
        },
        {
          start: "2025-09-01T00:00:00",
          end: "2025-09-03T00:00:00",
          id: "3",
          resource: "R4",
          text: "Event 3"
        }
      ];
      scheduler.update({resources, events});
    },
    eventsSortedByStart() {
      return scheduler.events
        .all()
        .sort((a, b) => a.start().getTime() - b.start().getTime());
    },
    init() {

      // previous
      this.elements.scrollToPrev.addEventListener("click", () => {
        const { start: viewportStart } = scheduler.getViewport();

        const prevEvent = this.eventsSortedByStart()
          .reverse()
          .find(e => e.end() <= viewportStart);

        if (prevEvent) {
          scheduler.scrollTo(prevEvent.start(), app.animatedScrolling);
        } else {
          scheduler.message("No previous event found");
        }
      });

      // next
      this.elements.scrollToNext.addEventListener("click", () => {
        const { end: viewportEnd } = scheduler.getViewport();

        const nextEvent = this.eventsSortedByStart()
          .find(e => e.start() >= viewportEnd);          // completely after the viewport

        if (nextEvent) {
          scheduler.scrollTo(nextEvent.start(), app.animatedScrolling);
        } else {
          scheduler.message("No next event found");
        }
      });

      // load initial data
      this.loadData();

    }
  };
  app.init();
</script>

</body>
</html>