Agent-readable demo data

Title
Progressive Row Rendering (JavaScript Scheduler) | DayPilot Pro for JavaScript Demo
Tree
DayPilot JavaScript Demo
Catalog root
Default
Description
Explore thousands of people and assignments, then jump to the last row. Compare progressive row rendering off and on, with simple labels or interactive row headers. Progressive rendering Rows 1,000 10,000 50,000 100,000 Layout People Teams and people Header content Plain text Rich content Interactive content Scroll to First Middle Last

Inline demo script 1

const dp = new DayPilot.Scheduler("dp", {
            startDate: "2026-10-01",
            days: 31,
            scale: "Day",
            cellWidth: 56,
            timeHeaders: [
                {groupBy: "Month", format: "MMMM yyyy"},
                {groupBy: "Day", format: "ddd d"}
            ],
            heightSpec: "Fixed",
            height: 500,
            rowHeaderWidth: 300,
            rowHeaderWidthAutoFit: false,
            rowMinHeight: 36,
            rowMarginTop: 6,
            rowMarginBottom: 6,
            eventHeight: 28,
            treeEnabled: true,
            progressiveRowRendering: true,
            // Only a few assignments are visible at once. Fill them immediately
            // on a jump so the row-header comparison has a complete viewport.
            scrollDelayEvents: 0,
            timeRangeSelectedHandling: "Disabled",
            eventMoveHandling: "Disabled",
            eventResizeHandling: "Disabled",
            onBeforeRowHeaderRender: args => {
                if (app.elements.content.value === "html") {
                    args.row.html = app.personLabel(args.row.data);
                }
            },
            onBeforeRowHeaderDomAdd: args => {
                if (app.elements.content.value === "dom") {
                    args.target = "Text";
                    args.element = app.createPerson(args.row.data);
                }
            }
        });
        dp.init();

        const app = {
            elements: {
                root: document.getElementById("rowheaderdemo"),
                enabled: document.getElementById("enabled"),
                count: document.getElementById("rowCount"),
                layout: document.getElementById("layout"),
                content: document.getElementById("contentMode"),
                renderTime: document.getElementById("renderTime"),
                dataset: document.getElementById("dataset"),
                hint: document.getElementById("contentHint")
            },
            // A row header can be removed when it leaves the viewport. Persistent
            // application state belongs outside the header's DOM element.
            markedRows: new Set(),
            data: null,
            dataKey: null,
            pending: 0,
            names: ["Avery Morgan", "Jordan Lee", "Sam Rivera", "Riley Chen", "Alex Parker", "Casey Blake", "Taylor Reed", "Morgan Ellis", "Jamie Quinn", "Drew Patel", "Skyler Evans", "Cameron Brooks"],
            roles: ["Design", "Engineering", "Quality assurance", "Product", "Operations"],
            teamNames: ["Product studio", "Platform team", "Customer experience", "Research team", "Operations team"],
            init() {
                this.addEventHandlers();
                this.applySettings();
            },
            addEventHandlers() {
                for (const control of [this.elements.enabled, this.elements.count, this.elements.layout, this.elements.content]) {
                    control.addEventListener("change", () => this.applySettings());
                }
                this.elements.root.querySelectorAll("[data-jump]").forEach(button => {
                    button.addEventListener("click", () => {
                        if (!this.data || dp.disposed()) {
                            return;
                        }
                        const index = Math.min(this.data.rows.length - 1, Math.floor(this.data.rows.length * Number(button.dataset.jump)));
                        const row = dp.rows.find(this.data.rows[index].id);
                        row.parent()?.expand();
                        dp.scrollToResource(row);
                    });
                });
            },
            async applySettings() {
                const request = ++this.pending;
                // Large eager DOM trees can block a browser for minutes. Keep
                // the public comparison responsive; the larger presets use
                // progressive rendering throughout.
                const large = Number(this.elements.count.value) > 10000;
                this.elements.enabled.disabled = large;
                if (large) {
                    this.elements.enabled.checked = true;
                }
                this.elements.enabled.parentElement.title = large
                    ? "Choose 10,000 rows or fewer to compare rendering off and on."
                    : "Render row headers near the viewport. Clear to render all visible rows in advance.";
                this.elements.renderTime.textContent = "Loading " + Number(this.elements.count.value).toLocaleString("en-US") + " rows…";
                this.elements.root.setAttribute("aria-busy", "true");
                // Let the browser display loading feedback before synchronous work.
                await new Promise(resolve => requestAnimationFrame(() => setTimeout(resolve, 0)));
                if (request !== this.pending || !this.elements.root.isConnected || dp.disposed()) {
                    return;
                }
                const dataKey = this.elements.count.value + ":" + this.elements.layout.value;
                const sameData = dataKey === this.dataKey;
                const scrollY = sameData ? dp.getScrollY() : 0;
                const scrollX = sameData ? dp.getScrollX() : 0;
                const dataStart = performance.now();
                if (!sameData) {
                    this.data = this.generateData(Number(this.elements.count.value), this.elements.layout.value === "teams");
                    this.dataKey = dataKey;
                }
                const dataMs = performance.now() - dataStart;
                const started = performance.now();
                dp.update({
                    resources: this.data.resources,
                    events: this.data.events,
                    progressiveRowRendering: this.elements.enabled.checked
                });
                dp.setScroll(scrollX, scrollY);
                const renderMs = performance.now() - started;
                this.elements.renderTime.textContent = "Rendering: " + renderMs.toFixed(0) + " ms";
                this.elements.renderTime.title = "Time spent updating the Scheduler and restoring the scroll position; browser painting follows. " + (sameData ? "Existing dataset reused." : "Data generation: " + dataMs.toFixed(0) + " ms.");
                this.elements.dataset.textContent = this.data.rows.length.toLocaleString("en-US") + " rows · " + this.data.events.length.toLocaleString("en-US") + " assignments";
                const teams = this.elements.layout.value === "teams";
                this.elements.hint.textContent = {
                    text: "All rows and assignments are loaded. " + (teams ? "Expand a team or jump through the list to explore." : "Jump through the list to explore."),
                    html: "People have initials, roles and unique numbers." + (teams ? " Expand or collapse teams using the arrows." : ""),
                    dom: "Bookmark a person or team, then scroll away and back. Your bookmarks stay selected."
                }[this.elements.content.value];
                if (large) {
                    this.elements.hint.textContent += " Progressive rendering stays on above 10,000 rows; choose a smaller preset for the off/on comparison.";
                }
                this.elements.root.setAttribute("aria-busy", "false");
            },
            generateData(count, teams) {
                const resources = [];
                const rows = [];
                const events = [];
                const dates = Array.from({length: 32}, (_, i) => dp.startDate.addDays(i).toString());
                for (let i = 0; i < count; i++) {
                    const team = teams && i % 10 === 0;
                    const name = team ? this.teamNames[Math.floor(i / 10) % this.teamNames.length] : this.names[i % this.names.length];
                    const number = String(i + 1).padStart(6, "0");
                    const resource = {
                        id: "R" + i,
                        name: name + " · " + number,
                        tags: {team, name, number, role: team ? "9 people" : this.roles[i % this.roles.length], tone: i % 4}
                    };
                    if (team) {
                        resource.children = [];
                        resource.expanded = true;
                    }
                    if (teams && !team) {
                        resources[resources.length - 1].children.push(resource);
                    }
                    else {
                        resources.push(resource);
                    }
                    rows.push(resource);
                    if (team) {
                        continue;
                    }
                    for (let j = 0; j < 2; j++) {
                        const day = (i * 3 % 6) + j * 10;
                        events.push({
                            id: i + "-" + j,
                            resource: resource.id,
                            start: dates[day],
                            end: dates[day + 3 + i % 3],
                            text: ["Website refresh", "Client onboarding", "Product launch", "Design review", "Platform update"][i % 5]
                        });
                    }
                }
                return {resources, rows, events};
            },
            personLabel(resource) {
                const tags = resource.tags;
                const initials = tags.name.split(" ").map(word => word[0]).slice(0, 2).join("").toUpperCase();
                return `<span class="person ${tags.team ? "team" : ""}">
                    <span class="avatar tone-${tags.tone}">${initials}</span>
                    <span><span class="person-name">${tags.name}</span><span class="person-detail">${tags.role} · ${tags.number}</span></span>
                </span>`;
            },
            createPerson(resource) {
                const container = document.createElement("div");
                container.className = "header-content";
                container.innerHTML = this.personLabel(resource);
                const button = document.createElement("button");
                button.type = "button";
                button.className = "mark-row";
                button.dataset.resource = resource.id;
                button.setAttribute("aria-label", "Bookmark " + resource.name);
                button.setAttribute("aria-pressed", String(this.markedRows.has(resource.id)));
                button.title = "Bookmark this row";
                button.innerHTML = '<svg width="14" height="18" 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>';
                button.addEventListener("click", event => {
                    event.stopPropagation();
                    if (this.markedRows.has(resource.id)) {
                        this.markedRows.delete(resource.id);
                    }
                    else {
                        this.markedRows.add(resource.id);
                    }
                    button.setAttribute("aria-pressed", String(this.markedRows.has(resource.id)));
                });
                container.appendChild(button);
                return container;
            }
        };

        app.init();