Keep the broadcast program in a
frozen row
while scrolling through crew bookings. Select a show to display its four assignment links.
Colors connect the show to its crew; the arrows describe assignments, not task dependencies.
Link shape: Right-angled Curved
Program row: Frozen at top Frozen at bottom
Match link colors to shows
Agent-readable demo data
Title Links with Frozen Rows (JavaScript Scheduler) | DayPilot Pro for JavaScript Demo
Tree DayPilot JavaScript Demo
Catalog root Default
Description Keep the broadcast program in a frozen row
while scrolling through crew bookings. Select a show to display its four assignment links.
Colors connect the show to its crew; the arrows describe assignments, not task dependencies.
Inline demo script 1
/* global DayPilot */
/* eslint-disable no-use-before-define -- Callbacks run after app initialization. */
const dp = new DayPilot.Scheduler("dp", {
startDate: "2027-10-18",
days: 1,
scale: "Hour",
showNonBusiness: false,
businessBeginsHour: 8,
businessEndsHour: 20,
businessWeekends: true,
timeHeaders: [{groupBy: "Day", format: "dddd, MMMM d, yyyy"}, {groupBy: "Hour", format: "H:mm"}],
cellWidthSpec: "Auto",
cellWidthMin: 65,
rowHeaderWidth: 215,
heightSpec: "Fixed",
height: 370,
eventHeight: 52,
useEventBoxes: "Never",
rowMarginTop: 2,
rowMarginBottom: 2,
durationBarVisible: false,
linkCrossGridEnabled: true,
linkShape: "RightAngled",
timeRangeSelectedHandling: "Disabled",
eventDeleteHandling: "Disabled",
onEventClick: args => app.select(args.e.data.tags.show),
onLinkClick: args => {
const booking = dp.events.find(args.link.data.to);
document.querySelector("#relation").textContent = `${booking.data.tags.role}: ${booking.data.tags.person}, assigned to ${booking.data.tags.showName}.`;
},
onBeforeEventRender: args => {
const show = app.shows.find(item => item.id === args.data.tags.show);
const focused = app.selected === show.id;
args.data.backColor = focused ? show.pale : "#f2f4f7";
args.data.borderColor = focused ? show.color : "#c4cbd4";
args.data.fontColor = focused ? "#1d3448" : "#687888";
if (args.data.resource === "program") {
args.data.backColor = focused ? show.color : "#e8edf2";
args.data.fontColor = focused ? "#ffffff" : "#4f647a";
}
}
});
const app = {
selected: "morning",
shows: [
{id: "morning", name: "Morning live", start: 9, end: 10.5, color: "#286aa6", pale: "#e4effa"},
{id: "midday", name: "Market report", start: 11.5, end: 13, color: "#168075", pale: "#e1f2ef"},
{id: "afternoon", name: "Studio guests", start: 14, end: 15.5, color: "#a66520", pale: "#fbefdf"},
{id: "evening", name: "Evening live", start: 17, end: 18.5, color: "#7a59a5", pale: "#f0eafa"}
],
roles: [
{name: "Producer", people: ["Maya Chen", "Owen Reed", "Sara Khan", "Leo Costa"], setup: 0.5, wrap: 0.25},
{name: "Camera", people: ["Nina Bell", "Arun Shah", "Eva Novak", "Sam Ortiz"], setup: 0.25, wrap: 0},
{name: "Sound", people: ["Alex Park", "Lina Berg", "Tom Ellis", "Noor Ali"], setup: 0.75, wrap: 0.5},
{name: "Captions", people: ["Zoe Martin", "Ben Lee", "Iris Wang", "Adam Brooks"], setup: 0, wrap: 0.75}
],
init() {
this.buildData();
dp.init();
this.render();
this.shows.forEach(show => {
const button = document.createElement("button");
button.type = "button";
button.dataset.show = show.id;
button.textContent = show.name;
button.addEventListener("click", () => this.select(show.id));
document.querySelector("#programs").appendChild(button);
});
this.select(this.selected);
document.querySelector("#shape").addEventListener("change", ev => dp.update({linkShape: ev.target.value}));
document.querySelector("#pin").addEventListener("change", () => this.render());
document.querySelector("#colors").addEventListener("change", () => this.render());
},
at(hours) {
return new DayPilot.Date("2027-10-18").addMinutes(hours * 60);
},
buildData() {
this.resources = [{id: "program", name: "Broadcast program", frozen: "top"}];
this.events = this.shows.map(show => ({id: show.id, resource: "program", text: show.name,
start: this.at(show.start), end: this.at(show.end), tags: {show: show.id}}));
this.links = [];
this.roles.forEach((role, roleIndex) => {
this.shows.forEach((show, showIndex) => {
const id = `crew-${roleIndex}-${showIndex}`;
const person = role.people[showIndex];
this.resources.push({id, name: `${person} · ${role.name}`});
this.events.push({id, resource: id, text: show.name, start: this.at(show.start - role.setup), end: this.at(show.end + role.wrap),
tags: {show: show.id, showName: show.name, role: role.name, person}});
this.links.push({id: `assignment-${id}`, from: show.id, to: id, type: "FinishToFinish", color: show.color, width: 2, tags: {show: show.id}});
});
});
},
select(id) {
this.selected = id;
this.render();
document.querySelectorAll("[data-show]").forEach(button => button.setAttribute("aria-pressed", String(button.dataset.show === id)));
const show = this.shows.find(item => item.id === id);
document.querySelector("#relation").textContent = `${show.name}: producer, camera, sound and captions. Other bookings stay visible as availability context.`;
},
render() {
const resources = this.resources.map(row => row.id === "program" ? {...row, frozen: document.querySelector("#pin").value} : row);
const links = this.links.filter(link => link.tags.show === this.selected)
.map(link => ({...link, color: document.querySelector("#colors").checked ? link.color : "#697787"}));
dp.update({resources, events: this.events, links});
}
};
app.init();