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>