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
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
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>