Features

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.

Live Demo

You can test this project in a live demo:

Highlighting Global Holidays (Grid Cells)

angular scheduler highlighting holidays global

DayPilot Angular Scheduler component lets you set custom background color for grid cells (see cell customization in the documentation). We will use this feature to highlight holidays. This sample only uses a custom background color - the drag and drop operations are not affected.

Lets have an array of holidays that we want to highlight:

export class SchedulerComponent implements AfterViewInit {

  // ...

  globalHolidays: any[] = [
    { start: '2021-09-15', end: '2020-09-15', backColor: '#fff2cc'}
  ];
  
  // ...

}

Each item in the array specifies the start and end dates and a background color to be applied.

We are using an all-day date format (start: "2021-09-15", end: "2021-09-15") instead of point-in-time format (start: "2021-09-15T00:00:00", end: "2021-09-16T00:00:00"). In case of holiday dates, this approach is more natural. However, since DayPilot works with exact date/time points internally we need to convert the end date manually (see below). See also Event End Date/Time topic in the documentation for more details.

Now we have the holiday dates defined so we can use the data to highlight the background cells using onBeforeCellRender event handler.

onBeforeCellRender: args => {
  let dp = this.scheduler.control;
  let component = this;

  let item = component.globalHolidays.find((range: any) => {
    let start = new DayPilot.Date(range.start);
    let end = new DayPilot.Date(range.end).addDays(1);
    return DayPilot.Util.overlaps(start, end, args.cell.start, args.cell.end);
  });

  if (item) {
    args.cell.backColor = item.backColor;
  }

}

The event handler is fired for every grid cell. It searches the array with holiday definitions (globalHolidays) for items that overlap with the current cell. The first item that matches is used to set the cell background color (args.cell.backColor).

Source code (scheduler.component.ts):

import {Component, ViewChild, AfterViewInit} from "@angular/core";
import {DayPilot, DayPilotSchedulerComponent} from "daypilot-pro-angular";
import {DataService} from "./data.service";{}

@Component({
  selector: 'scheduler-component',
  template: `<daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler>`,
  styles: [``]
})
export class SchedulerComponent implements AfterViewInit {

  @ViewChild("scheduler")
  scheduler!: DayPilotSchedulerComponent;

  events: any[] = [];

  globalHolidays: any[] = [
    { start: "2021-09-15", end: "2021-09-15", backColor: "#fff2cc", headerColor: "#f1c232"}
  ];

  config: DayPilot.SchedulerConfig = {
  
    // ...
  
    onBeforeCellRender: args => {
      let dp = this.scheduler.control;
      let component = this;

      let item = component.globalHolidays.find((range: any) => {
        let start = new DayPilot.Date(range.start);
        let end = new DayPilot.Date(range.end).addDays(1);
        return DayPilot.Util.overlaps(start, end, args.cell.start, args.cell.end);
      });

      if (item) {
        args.cell.backColor = item.backColor;
      }

    }

  };
  
  // ...

}

Highlighting Global Holidays (Time Header)

angular scheduler highlighting holidays global time header

Another option to display the global holidays is to add a color bar to the Scheduler time header.

The time header cells can be customized using a similar approach. There is a onBeforeTimeHeaderRender event handler which is fired for every cell in the time header. See also time header customization in the documentation.

onBeforeTimeHeaderRender: args => {
  let component = this;

  if (args.header.level === 1) {
    let item = component.globalHolidays.find((range: any) => {
      let start = new DayPilot.Date(range.start);
      let end = new DayPilot.Date(range.end).addDays(1);
      return DayPilot.Util.overlaps(start, end, args.header.start, args.header.end);
    });

    if (item) {
      let start = new DayPilot.Date(item.start);
      let end = new DayPilot.Date(item.end).addDays(1);

      args.header.areas = [
        { start: start, end: end, bottom: 0, height: 5, backColor: item.headerColor}
      ];
    }
  }
}

Our event handler searches the globalHolidays for an item that overlaps with the current cell. Note that only the cells in the second row are tested (args.header.level === 1).

If an overlapping record is found we insert an active area with the specified background color at the bottom of the time header cell.

Source code (scheduler.component.ts):

import {Component, ViewChild, AfterViewInit} from "@angular/core";
import {DayPilot, DayPilotSchedulerComponent} from "daypilot-pro-angular";
import {DataService} from "./data.service";{}

@Component({
  selector: 'scheduler-component',
  template: `<daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler>`,
  styles: [``]
})
export class SchedulerComponent implements AfterViewInit {

  @ViewChild("scheduler")
  scheduler!: DayPilotSchedulerComponent;

  events: any[] = [];

  globalHolidays: any[] = [
    { start: "2021-09-15", end: "2021-09-15", backColor: "#fff2cc", headerColor: "#f1c232"}
  ];

  config: DayPilot.SchedulerConfig = {
    
    // ...
    
    onBeforeTimeHeaderRender: args => {
      let component = this;

      if (args.header.level === 1) {
        let item = component.globalHolidays.find((range: any) => {
          let start = new DayPilot.Date(range.start);
          let end = new DayPilot.Date(range.end).addDays(1);
          return DayPilot.Util.overlaps(start, end, args.header.start, args.header.end);
        });

        if (item) {
          let start = new DayPilot.Date(item.start);
          let end = new DayPilot.Date(item.end).addDays(1);

          args.header.areas = [
            { start: start, end: end, bottom: 0, height: 5, backColor: item.headerColor}
          ];
        }
      }
    }
  };
  
  // ...

}

Highlighting Resource-Specific Holidays

angular scheduler highlighting holidays resource

We can also highlight holidays that are specific to certain resources (rows). We will add the holiday definitions directly to the resources array of the config:

resources: [
  { name: "Resource 1", id: "R1", holidays: [
    { start: "2021-09-05", end: "2021-09-10", backColor: "#f4cccc"}
  ]},
  { name: "Resource 2", id: "R2", holidays: [
    { start: "2021-09-04", end: "2021-09-06", backColor: "#d9ead3"}
  ]},
  { name: "Resource 3", id: "R3"},
  { name: "Resource 4", id: "R4"},
  { name: "Resource 5", id: "R5"},
  { name: "Resource 6", id: "R6"},
  { name: "Resource 7", id: "R7"},
  { name: "Resource 8", id: "R8"},
  { name: "Resource 9", id: "R9"},
  { name: "Resource 10", id: "R10"},
]

The format is the same - each item specifies start, end and a background color. The end date of the holiday is specified as a date-only value.

We will use onBeforeCellRender event handler to find overlapping records. This time, we will only search the holiday records stored in the resource data:

onBeforeCellRender: args => {
  let dp = this.scheduler.control;
  let component = this;

  let row = dp.rows.find(args.cell.resource);
  let holidays = row.data.holidays;
  if (!holidays) {
    return;
  }
  let item = holidays.find((range: any) => {
    let start = new DayPilot.Date(range.start);
    let end = new DayPilot.Date(range.end).addDays(1);
    return DayPilot.Util.overlaps(start, end, args.cell.start, args.cell.end);
  });

  if (item) {
    args.cell.backColor = item.backColor;
  }
},

Source code (scheduler.component.ts):

import {Component, ViewChild, AfterViewInit} from "@angular/core";
import {DayPilot, DayPilotSchedulerComponent} from "daypilot-pro-angular";
import {DataService} from "./data.service";{}

@Component({
  selector: 'scheduler-component',
  template: `<daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler>`,
  styles: [``]
})
export class SchedulerComponent implements AfterViewInit {

  @ViewChild("scheduler")
  scheduler!: DayPilotSchedulerComponent;

  events: any[] = [];

  config: DayPilot.SchedulerConfig = {
  
    // ...
  
    resources: [
      { name: "Resource 1", id: "R1", holidays: [
        { start: "2021-09-05", end: "2021-09-10", backColor: "#f4cccc"}
      ]},
      { name: "Resource 2", id: "R2", holidays: [
        { start: "2021-09-04", end: "2021-09-06", backColor: "#d9ead3"}
      ]},
      { name: "Resource 3", id: "R3"},
      { name: "Resource 4", id: "R4"},
      { name: "Resource 5", id: "R5"},
      { name: "Resource 6", id: "R6"},
      { name: "Resource 7", id: "R7"},
      { name: "Resource 8", id: "R8"},
      { name: "Resource 9", id: "R9"},
      { name: "Resource 10", id: "R10"},
    ],
    onBeforeCellRender: args => {
      let dp = this.scheduler.control;
      let component = this;

      let row = dp.rows.find(args.cell.resource);
      let holidays = row.data.holidays;
      if (!holidays) {
        return;
      }
      let item = holidays.find((range: any) => {
        let start = new DayPilot.Date(range.start);
        let end = new DayPilot.Date(range.end).addDays(1);
        return DayPilot.Util.overlaps(start, end, args.cell.start, args.cell.end);
      });

      if (item) {
        args.cell.backColor = item.backColor;
      }
    },
  };

  // ...

}

Source Code

Here is the full source code of our Angular Scheduler component that highlights global and resource-specific holidays. This file is also included in the download package with the Angular project (see the article header).

src/app/scheduler.component.ts

import {Component, ViewChild, AfterViewInit} from '@angular/core';
import {DayPilot, DayPilotSchedulerComponent} from 'daypilot-pro-angular';
import {DataService} from './data.service'; {}

@Component({
  selector: 'scheduler-component',
  template: `<daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler>`,
  styles: [``]
})
export class SchedulerComponent implements AfterViewInit {

  @ViewChild('scheduler')
  scheduler!: DayPilotSchedulerComponent;

  events: any[] = [];

  globalHolidays: any[] = [
    { start: '2021-09-15', end: '2021-09-15', backColor: '#fff2cc', headerColor: '#f1c232'}
  ];

  config: DayPilot.SchedulerConfig = {
    cellWidthSpec: 'Auto',
    timeHeaders: [{groupBy: 'Month'}, {groupBy: 'Day', format: 'd'}],
    scale: 'Day',
    days: 30,
    startDate: '2021-09-01',
    timeRangeSelectedHandling: 'Disabled',
    resources: [
      { name: 'Resource 1', id: 'R1', holidays: [
        { start: '2021-09-05', end: '2021-09-10', backColor: '#f4cccc'}
      ]},
      { name: 'Resource 2', id: 'R2', holidays: [
        { start: '2021-09-04', end: '2021-09-06', backColor: '#d9ead3'}
      ]},
      { name: 'Resource 3', id: 'R3'},
      { name: 'Resource 4', id: 'R4'},
      { name: 'Resource 5', id: 'R5'},
      { name: 'Resource 6', id: 'R6'},
      { name: 'Resource 7', id: 'R7'},
      { name: 'Resource 8', id: 'R8'},
      { name: 'Resource 9', id: 'R9'},
      { name: 'Resource 10', id: 'R10'},
    ],
    onBeforeCellRender: args => {
       const dp = this.scheduler.control;
       const component = this;

       (function applyResourceHolidays() {

        const row = dp.rows.find(args.cell.resource);
        const holidays = row.data.holidays;
        if (!holidays) {
          return;
        }
        const item = holidays.find((range: any) =>{
          const start = new DayPilot.Date(range.start);
          const end = new DayPilot.Date(range.end).addDays(1);
          return DayPilot.Util.overlaps(start, end, args.cell.start, args.cell.end);
        });

        if (item) {
          args.cell.properties.backColor = item.backColor;
        }
      })();

       (function applyGlobalHolidays() {
        const item = component.globalHolidays.find((range) => {
          const start = new DayPilot.Date(range.start);
          const end = new DayPilot.Date(range.end).addDays(1);
          return DayPilot.Util.overlaps(start, end, args.cell.start, args.cell.end);
        });

        if (item) {
          args.cell.properties.backColor = item.backColor;
        }
      })();
    },
    onBeforeTimeHeaderRender: args => {
      const component = this;

      if (args.header.level === 1) {
        const item = component.globalHolidays.find((range) => {
          const start = new DayPilot.Date(range.start);
          const end = new DayPilot.Date(range.end).addDays(1);
          return DayPilot.Util.overlaps(start, end, args.header.start, args.header.end);
        });

        if (item) {
          const start = new DayPilot.Date(item.start);
          const end = new DayPilot.Date(item.end).addDays(1);

          args.header.areas = [
            { start, end, bottom: 0, height: 5, backColor: item.headerColor}
          ];
        }
      }
    }
  };

  constructor(private ds: DataService) {
  }

  ngAfterViewInit(): void {

    const from = this.scheduler.control.visibleStart();
    const to = this.scheduler.control.visibleEnd();
    this.ds.getEvents(from, to).subscribe(result => {
      this.events = result;
    });
  }

}