Features

  • Built using Angular Resource Calendar component from DayPilot Pro for JavaScript
  • The resource calendar displays resources (people, rooms, tools) as columns (the horizontal axis)
  • The columns can be arranged in a multi-level hierarchy
  • Time is displayed on the vertical axis (up to 24 hours per column)
  • Includes a trial version of DayPilot Pro for JavaScript (see License below)

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. Buy a license.

Angular Resource Calendar Configurator

This project includes the minimum setup required for the Angular resource calendar to run, including the boilerplate code and module dependencies. You can use it as a starting point for your own implementation.

You can also use the Resource Calendar UI Builder - an online configurator application that lets you experiment with different resource calendar configurations. All changes are immediately visible in a live preview. You can copy the configuration object (and replace it in an existing project) or generate a downloadable Angular project (Angular 5 and Angular 6 are supported).

Initialization

Before running the Angular application, it's necessary to download the dependencies specified in package.json. Simply run "npm install" (Node.js with npm package manager is required):

npm install

When the dependencies are downloaded to node_modules you can run the application:

npm start

This will start a built-in web server on port 4200. You can open the application in the browser at http://localhost:4200.

Angular 6 Project Structure

The Angular project structure is based on the default structure which is created by Angular CLI. We will minimize changes made to the generated files - all custom code can be found in src/app/calendar directory, encapsulated in a special CalendarModule.

The CalendarModule consists of three files:

  • data.service.ts
  • calendar.component.ts
  • calendar.module.ts

Resource Calendar Component (calendar.component.ts)

The resource calendar is built using DayPilot Angular event calendar component. 

By default, this component displays a traditional calendar with one or more days as columns (day, week, workweek views):

angular-event-calendar-week-view.png

However, you can easily switch it to resources mode which can display custom columns:

angular-resource-calendar-columns.png

The vertical axis displays time of day. By default, the day starts and ands at 12am (midnight) but you can also use custom start and end hours - this way you can display less then 24 hours on the time axis. The time span can continue over midnight (if the start hour is smaller than the end hour). This is useful when you need to scheduler shift-based activities that span multiple days. Anyway, our instance uses the default time axis configuration.

Note: The Calendar component can only display resources on the horizontal axis (as columns). If you want to display resources on the vertical axis (as rows) you can use the Scheduler component.

The calendar.component.ts file defines CalendarComponent class, which is a wrapper around the DayPilot calendar component. It adds configuration properties and custom event handlers.

calendar.component.ts

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

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

  @ViewChild("calendar")
  calendar: DayPilotCalendarComponent;

  events: any[] = [];

  config: any = {
    viewType: "Resources",
    headerLevels: 2,
    headerHeight: 25,
    onTimeRangeSelected: args => {
      DayPilot.Modal.prompt("Create a new event:", "Event 1").then(function(modal) {
        let dp = this.calendar.control;
        dp.clearSelection();
        if (!modal.result) { return; }
        dp.events.add(new DayPilot.Event({
          start: args.start,
          end: args.end,
          id: DayPilot.guid(),
          text: modal.result,
          resource: args.resource
        }));
      });
    }
  };

  constructor(private ds: DataService) {
  }

  ngAfterViewInit(): void {
    this.ds.getResources().subscribe(result => this.config.columns = result);

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

}

The configuration object starts with viewType property. This is required to switch the calendar component to the resource calendar mode:

config: any = {
  viewType: "Resources",
  // ...
};

Next, it sets the headerLevels property value to 2. This allows us to display a 2-level hierarchy of columns (by default the resource calendar displays just a single level).

config: any = {
  // ...
  headerLevels: 2,
  // ...
};

The column data are loaded using a special DataService class (which wraps all the data-access code) - we will get to this class later. The columns can also be defined statically using columns property:

config: any = {
  // ...
  headerLevels: 2,
  columns: [
    { name: "Group A", id: "GA", children: [
        { name: "Resource 1", id: "R1"},
        { name: "Resource 2", id: "R2"},
        { name: "Resource 3", id: "R3"},
        { name: "Resource 4", id: "R4"}
      ]},
    { name: "Group B", id: "GB", children: [
        { name: "Resource 5", id: "R5"},
        { name: "Resource 6", id: "R6"},
        { name: "Resource 7", id: "R7"},
        { name: "Resource 8", id: "R8"}
      ]}
  ];
  // ...
};

The header of our resource calendar now looks like this:

angular-resource-calendar-column-hierarchy.png

Each column has its own ID which will be used to display the correct events (each column will only include events with a matching resource id).

Handling User Events

The CalendarComponent class defines an onTimeRangeSelected event handler. This event is fired when a user selects a time range - we will use the event handler to create a new event at the selected location.

config: any = {
  // ...
  onTimeRangeSelected: args => {
    DayPilot.Modal.prompt("Create a new event:", "Event 1").then(function(modal) {
      let dp = this.calendar.control;
      dp.clearSelection();
      if (!modal.result) { return; }
      dp.events.add(new DayPilot.Event({
        start: args.start,
        end: args.end,
        id: DayPilot.guid(),
        text: modal.result,
        resource: args.resource
      }));
    });
  }
};

This implementation opens a modal dialog for entering an event text, and adds a new event. In a real application, you'd also want to add a call to the server to save the new event in a database.

You can define more event handlers to implement custom handling of other user actions, such as drag and drop event moving and resizing.

Loading Data (data.service.ts)

The ngAfterViewInit() method of our resource calendar component requests the column and event data using a special DataService class:

constructor(private ds: DataService) {}

ngAfterViewInit(): void {
  this.ds.getResources().subscribe(result => this.config.columns = result);

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

The DataService class contains all data-loading code. Our implementation is static - it defines the columns and events using an array. In a real-world application, you'd want to change the getResources() and getEvents() method implementations to make an HTTP call to a server to get the data from a database.

data.service.ts

import {Injectable} from "@angular/core";
import {Observable} from "rxjs";
import {DayPilot} from "daypilot-pro-angular";
import {HttpClient} from "@angular/common/http";

@Injectable()
export class DataService {

  events: any[] = [
    {
      id: 1,
      start: DayPilot.Date.today().addHours(12),
      end: DayPilot.Date.today().addHours(14),
      text: "Event 1",
      resource: "GA"
    },
    {
      id: 2,
      start: DayPilot.Date.today().addHours(10),
      end: DayPilot.Date.today().addHours(12),
      text: "Event 2",
      resource: "R1"
    }
  ];

  resources: any[] = [
    { name: "Group A", id: "GA", children: [
        { name: "Resource 1", id: "R1"},
        { name: "Resource 2", id: "R2"},
        { name: "Resource 3", id: "R3"},
        { name: "Resource 4", id: "R4"}
      ]},
    { name: "Group B", id: "GB", children: [
        { name: "Resource 5", id: "R5"},
        { name: "Resource 6", id: "R6"},
        { name: "Resource 7", id: "R7"},
        { name: "Resource 8", id: "R8"}
      ]}
  ];

  constructor(private http : HttpClient){
  }

  getEvents(from: DayPilot.Date, to: DayPilot.Date): Observable<any[]> {

    // simulating an HTTP request
    return new Observable(observer => {
      setTimeout(() => {
        observer.next(this.events);
      }, 200);
    });

    // return this.http.get("/api/events?from=" + from.toString() + "&to=" + to.toString());
  }

  getResources(): Observable<any[]> {

    // simulating an HTTP request
    return new Observable(observer => {
      setTimeout(() => {
        observer.next(this.resources);
      }, 200);
    });

    // return this.http.get("/api/resources");
  }

}