Choose a timeline, then scroll or take an animated jump to the end. Try switching progressive time header rendering off and on with a longer timeline.
// Scheduler configuration: an hourly year with progressive time headers.
// The same configuration is reused when the demo creates a fresh Scheduler.
const schedulerConfig = {
startDate: "2026-01-01",
days: 365,
scale: "Hour",
cellWidth: 44,
timeHeaders: [
{groupBy: "Month", format: "MMMM yyyy", height: 32},
{groupBy: "Day", format: "ddd d", height: 52},
{groupBy: "Hour", format: "h tt", height: 28}
],
progressiveTimeHeaderRendering: true,
rowHeaderWidth: 140,
rowHeaderWidthAutoFit: false,
heightSpec: "Auto",
rowMinHeight: 42,
timeRangeSelectedHandling: "Disabled",
resources: [
{name: "Room A", id: "A"},
{name: "Room B", id: "B"},
{name: "Room C", id: "C"}
],
onBeforeTimeHeaderRender: args => {
const groupBy = args.control.timeHeaders[args.header.level].groupBy;
if (groupBy !== "Day") {
return;
}
const dayOfWeek = args.header.start.getDayOfWeek();
if (dayOfWeek === 0 || dayOfWeek === 6) {
args.header.cssClass = "weekend";
}
if (app.elements.content.value === "html") {
args.header.html = app.dateLabel(args.header.start);
}
}
};
let dp = new DayPilot.Scheduler("dp", schedulerConfig);
// Demo controls: timeline presets, content selection and animated navigation.
const app = {
elements: {
enabled: document.getElementById("enabled"),
preset: document.getElementById("preset"),
content: document.getElementById("contentMode"),
initTime: document.getElementById("initTime"),
contentHint: document.getElementById("contentHint")
},
presets: {
month: {
days: 31,
scale: "Day",
cellDuration: 1440,
cellWidth: 84
},
year: {
days: 365,
scale: "Hour",
cellDuration: 60,
cellWidth: 44
},
week: {
days: 7,
scale: "CellDuration",
cellDuration: 1,
cellWidth: 40
},
minuteMonth: {
days: 31,
scale: "CellDuration",
cellDuration: 1,
cellWidth: 40
},
decade: {
days: 3652,
scale: "Hour",
cellDuration: 60,
cellWidth: 44
}
},
timeHeaders: {
Day: [
{groupBy: "Month", format: "MMMM yyyy", height: 32},
{groupBy: "Day", format: "ddd d", height: 52}
],
Hour: [
{groupBy: "Month", format: "MMMM yyyy", height: 32},
{groupBy: "Day", format: "ddd d", height: 52},
{groupBy: "Hour", format: "h tt", height: 28}
],
CellDuration: [
{groupBy: "Day", format: "ddd d MMM", height: 52},
{groupBy: "Hour", format: "h tt", height: 28},
{groupBy: "Cell", format: "mm", height: 28}
]
},
contentHints: {
text: "Simple date labels keep the focus on scrolling through the timeline.",
html: "Calendar labels pair the weekday with the date. Weekends have a subtle gray background.",
dom: "Click a bookmark to mark a date, then scroll away and back. Your marked dates stay selected."
},
// Keep application state outside header elements, which can be evicted.
markedDates: new Set(),
activePreset: "year",
init(initializationTime) {
this.showInitializationTime(initializationTime);
this.updateContentHint();
this.addEventHandlers();
},
addEventHandlers() {
this.elements.enabled.addEventListener("change", () => this.applySettings());
this.elements.preset.addEventListener("change", () => this.applySettings());
this.elements.content.addEventListener("change", () => this.applySettings());
document.querySelectorAll("[data-jump]").forEach(button => {
button.addEventListener("click", () => {
const fraction = Number(button.dataset.jump);
const date = dp.startDate.addDays(Math.floor(dp.days * fraction));
dp.scrollTo(date, "fast");
});
});
},
applySettings() {
const presetName = this.elements.preset.value;
const preset = this.presets[presetName];
// Preserve the position when comparing content or rendering modes.
const scrollX = presetName === this.activePreset ? dp.getScrollX() : 0;
dp.dispose();
const options = {
...schedulerConfig,
days: preset.days,
scale: preset.scale,
cellDuration: preset.cellDuration,
cellWidth: preset.cellWidth,
timeHeaders: this.timeHeaders[preset.scale],
progressiveTimeHeaderRendering: this.elements.enabled.checked
};
if (this.elements.content.value === "dom") {
options.onBeforeTimeHeaderDomAdd = args => {
const groupBy = args.control.timeHeaders[args.header.level].groupBy;
if (groupBy === "Day") {
args.element = this.createDateButton(args.header.start);
}
};
}
dp = new DayPilot.Scheduler("dp", options);
dp.scrollTo(scrollX);
// Recreate the Scheduler so every reading measures a fresh init().
const started = performance.now();
dp.init();
this.showInitializationTime(performance.now() - started);
this.activePreset = presetName;
this.updateContentHint();
},
showInitializationTime(milliseconds) {
this.elements.initTime.textContent = "Initialization: " + milliseconds.toFixed(0) + " ms";
this.elements.initTime.title = "Time spent in init(); browser painting follows.";
},
updateContentHint() {
this.elements.contentHint.textContent = this.contentHints[this.elements.content.value];
},
dateLabel(date) {
return `<span class="date-label">
<span class="date-weekday">${date.toString("ddd")}</span>
<span class="date-number">${date.toString("d MMM")}</span>
</span>`;
},
createDateButton(date) {
const key = date.toString("yyyy-MM-dd");
const button = document.createElement("button");
button.type = "button";
button.className = "mark-date";
button.dataset.date = key;
button.setAttribute("aria-label", "Mark " + date.toString("dddd, MMMM d, yyyy"));
button.setAttribute("aria-pressed", String(this.markedDates.has(key)));
button.title = "Mark this date";
button.innerHTML = `<svg class="mark-icon" viewBox="0 0 16 20" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
<path d="M3 2h10v16l-5-3-5 3z"/>
</svg>` + this.dateLabel(date);
button.addEventListener("click", event => {
event.stopPropagation();
if (this.markedDates.has(key)) {
this.markedDates.delete(key);
}
else {
this.markedDates.add(key);
}
button.setAttribute("aria-pressed", String(this.markedDates.has(key)));
});
return button;
}
};
// Initialize after app is available to the header-content callbacks.
const initializationStart = performance.now();
dp.init();
app.init(performance.now() - initializationStart);