
| Demo | |
|---|---|
| Downloads | |
| Languages | TypeScript |
| Technologies | angular |
Overview
This tutorial shows how to implement disabled slots in the DayPilot Angular Scheduler component. For an introduction to using the Angular Scheduler component, please see the Angular Scheduler Tutorial which explains how to install and configure the Angular Scheduler component and how to load the schedule data from the server using HTTP calls.
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.
How to disable time slots in the Angular Scheduler component?
The Angular Scheduler component prevents selecting the disabled slots (when creating new events) and moving existing events to a new location if they overlap a disabled slot. The same rule is applied during resizing as well.
The disabled slots can be defined by extending the items of the resources array. We will use a custom property called disabled. This is an arbitrary name and you can change it but make sure it doesn’t conflict with the existing property names.
The disabled property holds an array of time ranges (defined using start and end properties). The start and end properties can be DayPilot.Date objects or strings that can be converted to DayPilot.Date (ISO 8601 format without time zone).
resources: DayPilot.ResourceData[] = [
{ name: 'Group A', id: 'GA', expanded: true, children: [
{ name: 'Resource 1', id: 'R1' },
{ name: 'Resource 2', id: 'R2', disabled: [
{start: "2027-07-05T00:00:00", end: "2027-07-12T00:00:00"}
]
}
]},
{ name: 'Group B', id: 'GB', expanded: true, children: [
{ name: 'Resource 3', id: 'R3', disabled: [
{start: "2027-07-05T00:00:00", end: "2027-07-10T00:00:00"}
]
},
{ name: 'Resource 4', id: 'R4'}
]}
];
To actually disable the time slots, it is necessary to use onBeforeCellRender event handler. This event is fired for every time slot of the Angular Scheduler and it lets you define custom slot properties, including disabled status. In addition to marking the slot as disabled, we also apply a custom background color.
config = signal<DayPilot.SchedulerConfig>({
// ...
onBeforeCellRender: (args) => {
const dp = this.scheduler.control;
const row = dp.rows.find(args.cell.resource);
const disabled = row.data.disabled;
if (!disabled) {
return;
}
const disabledSlot = disabled.map(this.activateDates).find((item: Range) => DayPilot.Util.overlaps(item.start, item.end, args.cell.start, args.cell.end));
const color = "#dd7e6b";
if (disabledSlot) {
args.cell.properties.backColor = color;
args.cell.properties.disabled = true;
}
},
// ...
});
The configuration is declared with signal, imported from @angular/core, and bound using [config]="config". The activateDates() helper converts resource range strings to DayPilot.Date values before comparing them.
activateDates(item: Range) {
return {start: new DayPilot.Date(item.start), end: new DayPilot.Date(item.end)};
}
The event handler uses the DayPilot.Util.overlaps() method to check if any of the ranges defined in the disabled array overlap with the current slot.
How to change the time slot status to “disabled” using drag and drop?
The previous example used predefined ranges that were applied when the Angular Scheduler was rendered.
In this step, we will add an option to add disabled ranges to the row data using drag and drop.
The users will be able to select a time range using the right mouse button. As soon as the selection is completed (on mouse button release) the Angular Scheduler will display a context menu with the available actions.
First, you need to define the context menu for the time range selection using contextMenuSelection property of the Angular Scheduler config:
config = signal<DayPilot.SchedulerConfig>({
// ...
contextMenuSelection: new DayPilot.Menu({
onHide: args => {
this.scheduler.control.clearSelection();
},
items: [
{
text: "Disable",
onClick: args => {
const selection = args.source;
const row = this.scheduler.control.rows.find(selection.resource);
if (!row.data.disabled) {
row.data.disabled = [];
}
const originalActivated = row.data.disabled.map(this.activateDates);
const overlapping = originalActivated.filter((item: Range) => {
return DayPilot.Util.overlaps(item.start, item.end, selection.start, selection.end);
});
if (overlapping.length === 0) {
row.data.disabled.push({
start: selection.start,
end: selection.end
});
}
else {
let minStart = selection.start;
let maxEnd = selection.end;
overlapping.forEach((item: Range) => {
if (item.start < minStart) {
minStart = item.start;
}
if (item.end > maxEnd) {
maxEnd = item.end;
}
});
row.data.disabled = originalActivated.filter((item: Range) => {
return !overlapping.includes(item);
});
row.data.disabled.push({
start: minStart,
end: maxEnd
});
}
this.refreshResources();
}
},
// ...
]
}),
// ...
});
refreshResources(): void {
this.config.update(config => ({ ...config, resources: [...(config.resources ?? [])] }));
}
The onClick handler of the “Disable” menu item updates the disabled ranges defined for the given row.
It also checks for any existing disabled ranges and merges the selection with them in case there is an overlap.
Here you can see it in action - the selection partially overlaps with the existing disabled range:
The Angular Scheduler will merge both ranges into a new one:
In order to allow selection of the cells that are already blocked, you need to ignore the disabled cells when selecting a time range using the right mouse button:
onTimeRangeSelecting: args => {
if (args.button === "right") {
args.ignoreDisabledCells = true;
}
},
How to enable time slots using drag and drop?
In this step, you’ll see how to add the “Enable” option to the context menu. This will let users enable the disabled slots again.
The onClick handler of the “Enable” context menu item finds the existing time ranges for the selected row and subtracts the new range from them. Then it updates the array of disabled ranges.
The sample stores its configuration in an Angular signal. Both menu actions call refreshResources() after changing the row data. This publishes a new configuration object and resources array, so the Scheduler repaints the cells. Mutating row.data.disabled alone does not notify the signal.
config = signal<DayPilot.SchedulerConfig>({
// ...
contextMenuSelection: new DayPilot.Menu({
// ...
items: [
// ...
{
text: "Enable",
onClick: args => {
const selection = args.source;
const row = this.scheduler.control.rows.find(selection.resource);
if (!row.data.disabled) {
row.data.disabled = [];
}
const originalActivated = row.data.disabled.map(this.activateDates);
originalActivated.sort((a: Range, b: Range) => {
return a.start.getTime() - b.start.getTime();
});
let keep = originalActivated.filter((item: Range) => !DayPilot.Util.overlaps(selection.start, selection.end, item.start, item.end));
const overlaps = originalActivated.filter((item: Range) => DayPilot.Util.overlaps(selection.start, selection.end, item.start, item.end));
overlaps.forEach((item: Range )=> {
const fragments = this.minus(item.start, item.end, selection.start, selection.end);
keep = keep.concat(fragments);
});
row.data.disabled = keep;
this.refreshResources();
}
}
]
}),
// ...
});
// ...
minus(start1: DayPilot.Date, end1: DayPilot.Date, start2: DayPilot.Date, end2: DayPilot.Date): Range[] {
const fragments: Range[] = [];
if (start1 < start2) {
fragments.push({start: start1, end: start2});
}
if (end2 < end1) {
fragments.push({start: end2, end: end1});
}
return fragments;
}
The minus() helper is called only for overlapping ranges. It keeps a fragment before the selection when one exists, and another after it when one exists. Selecting the whole disabled range removes it; selecting its middle splits it in two. Range ends are exclusive: a range ending on July 7 leaves July 7 available.
This is how the updated row looks after applying the changes:
You can download the attached Angular project for full source code. You can run the project locally to see how the feature works.
DayPilot




