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