The timeline extends beyond the visible dates. When scrolling approaches either edge, the timeline shifts to provide more space, keeping the visible dates in place.
const dp = new DayPilot.Scheduler("dp", {
startDate: "2026-10-01",
scale: "Day",
cellWidth: 40,
timeHeaders: [
{groupBy: "Month", format: "MMMM yyyy"},
{groupBy: "Day", format: "d"}
],
infiniteScrollingEnabled: "Auto",
days: 300,
infiniteScrollingStepDays: 70,
infiniteScrollingMargin: 200,
rowHeaderWidth: 180,
heightSpec: "Fixed",
height: 360,
resources: Array.from({length: 12}, (_, i) => ({id: "R" + i, name: "Resource " + (i + 1)})),
dynamicLoading: true,
scrollDelayEvents: 0,
eventMoveHandling: "Disabled",
eventResizeHandling: "Disabled",
timeRangeSelectedHandling: "Disabled",
onScroll: (args) => {
args.events = app.events(args.viewport);
args.loaded();
}
});
const app = {
events(viewport) {
const list = [];
const origin = new DayPilot.Date("2026-01-01");
const interval = 3;
const first = Math.floor(new DayPilot.Duration(viewport.start.ticks - origin.ticks).totalDays() / interval) - 1;
const last = Math.ceil(new DayPilot.Duration(viewport.end.ticks - origin.ticks).totalDays() / interval);
for (let slot = first; slot <= last; slot++) {
for (let row = 0; row < dp.resources.length; row++) {
const from = origin.addDays(slot * interval + (row % 3));
list.push({
id: row + ":" + from.toString(),
resource: "R" + row,
start: from,
end: from.addDays(2),
text: ["Planning", "Delivery", "Review", "Preparation"][row % 4],
backColor: row % 2 ? "#dcfce7" : "#dbeafe",
barColor: row % 2 ? "#16a34a" : "#2563eb"
});
}
}
return list;
},
read() {
const mode = document.getElementById("scrolling-mode").value;
return {
infiniteScrollingEnabled: mode === "Auto" ? "Auto" : mode === "true",
days: Number(document.getElementById("range-days").value),
infiniteScrollingStepDays: Number(document.getElementById("step-days").value),
infiniteScrollingMargin: Number(document.getElementById("margin").value)
};
},
apply() {
const form = document.getElementById("infinite-settings");
if (!form.reportValidity()) {
return;
}
const options = this.read();
const anchor = dp.getViewport().start;
const message = document.getElementById("settings-message");
try {
if (!options.infiniteScrollingEnabled) {
options.startDate = anchor.getDatePart();
}
dp.update(options);
dp.scrollTo(anchor);
this.previous = options;
message.hidden = true;
} catch (error) {
dp.update(this.previous);
dp.scrollTo(anchor);
message.textContent = error.message || String(error);
message.hidden = false;
}
},
showFields() {
const mode = document.getElementById("scrolling-mode").value;
document.getElementById("manual-settings").hidden = mode === "Auto";
document.getElementById("step-setting").hidden = mode === "false";
document.getElementById("margin-setting").hidden = mode === "false";
document.getElementById("settings-help").hidden = mode === "false";
},
scrollTo(date) {
try {
dp.scrollTo(date);
} catch (error) {
const message = document.getElementById("settings-message");
message.textContent = error.message || String(error);
message.hidden = false;
}
},
init() {
this.previous = this.read();
const form = document.getElementById("infinite-settings");
form.addEventListener("change", () => {
this.showFields();
this.apply();
});
form.addEventListener("submit", (event) => {
event.preventDefault();
this.apply();
});
for (const [id, date] of [
["jan1", "2026-01-01"],
["jun1", "2026-06-01"],
["nov1", "2026-11-01"]
]) {
document.getElementById(id).addEventListener("click", () => this.scrollTo(date));
}
document
.getElementById("minus")
.addEventListener("click", () => this.scrollTo(dp.getViewport().start.addMonths(-1)));
document
.getElementById("plus")
.addEventListener("click", () => this.scrollTo(dp.getViewport().start.addMonths(1)));
document.getElementById("today").addEventListener("click", () => this.scrollTo(DayPilot.Date.today()));
document.getElementById("scroll-date-go").addEventListener("click", () => {
const field = document.getElementById("scroll-date");
if (field.value && field.reportValidity()) {
this.scrollTo(field.value);
}
});
}
};
// Dynamic loading calls app.events() during the initial render.
dp.init();
app.init();