Features

This tutorial shows how to manage resources (displayed as rows) using the built-in UI elements.

  • Angular Scheduler component that displays a timeline for multiple resources

  • How to load resources from the server-side backend and display them as rows

  • How to add a row header context menu (including a hint icon)

  • How to create resource groups using an inline edit box

  • How to create child resources using a context menu and modal dialog

  • How to delete resources using context menu

  • You can move resources using drag and drop

  • The Angular project includes a trial version of DayPilot Pro for JavaScript (see License below)

This tutorial focuses on managing resources (rows). For an introduction to the Angular Scheduler component please see the basic tutorial:

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 Load Resources and Display Them as Rows in the Scheduler

angular scheduler resource management loading data

The Angular Scheduler component displays resources on the vertical axis.

To specify the resources, use the resources property of the using the config object. It’s an array of ResourceData objects:

  resources: DayPilot.ResourceData[] = [
    { name: "Group 1", id: "G1", expanded: true, children: [
      { name: "Resource 1", id: "R1"},
      { name: "Resource 2", id: "R2"},
    ]},
    { name: "Group 2", id: "G2", expanded: true, children: [
      { name: "Resource 3", id: "R3"},
      { name: "Resource 4", id: "R4"},
    ]},
  ];

We will use a DataService class to load the data. The sample DataService implementation uses locally-generated data for simplicity. In your application, you can replace this logic with HTTP calls that load data using a server-side backend.

The resources are loaded using DataService in ngAfterViewInit() method:

this.ds.getResources().subscribe(result => this.config.resources = result);

By default, the Scheduler displays a flat list of resources. We want to use a hierarchy of rows - that needs to be enabled using treeEnabled property of the config object:

  config: DayPilot.SchedulerConfig = {
    treeEnabled: true,
  };

A simplified version of SchedulerComponent that loads resources and events from the data source:

scheduler.component.ts

import {Component, ViewChild, AfterViewInit, ChangeDetectorRef} 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 = {
    timeHeaders : [
      {groupBy: "Month", format: "MMMM yyyy"},
      {groupBy: "Day", format: "d"}
    ],
    eventHeight: 40,
    scale: "Day",
    days: 31,
    startDate: "2023-01-01",
    treeEnabled: true,
    // ...
  };


  constructor(private ds: DataService) {}

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

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

}

How to Add a Context Menu to the Row Headers

angular scheduler resource management context menu

We want to let users access resource-related actions using a context menu. Our context menu will have three items:

  • Edit

  • Delete

  • Add Child

The menu can be created using DayPilot.Menu object:

  menu: DayPilot.Menu = new DayPilot.Menu({
    items: [
      {text: "Edit..." },
      {text: "Delete" },
      {text: "-"},   // separator
      {text: "Add Child..."}
    ]
  });

Next, we will add an onClick event handler to each of the items:

  menu: DayPilot.Menu = new DayPilot.Menu({
    items: [
      {
        text: "Edit...",
        onClick: async args => {
          const form = [
            {name: "Resource name", id: "name"}
          ];
          const row = args.source;
          const data = row.data;
          const modal = await DayPilot.Modal.form(form, data);
          if (modal.canceled) {
            return;
          }
          const name = modal.result.name;
          this.ds.updateResource(row, name).subscribe(result => {
            row.data.name = name;
          });
        }
      },
      {
        text: "Delete",
        onClick: args => {
          let row = args.source;
          this.ds.deleteResource(row).subscribe(result => {
            row.remove();
            this.scheduler.control.message("Deleted");
          });
        }
      },
      {
        text: "-"
      },
      {
        text: "Add Child...",
        onClick: async args => {
          const form = [
            {name: "Resource name", id: "name"}
          ];
          const row = args.source;
          const data = {};
          const modal = await DayPilot.Modal.form(form, data);
          if (modal.canceled) {
            return;
          }
          const name = modal.result.name;
          this.ds.createResource(row, name).subscribe(resource => {

            // add the new resource to the tree
            let parent = row.data;
            parent.expanded = true;
            if (!parent.children) {
              parent.children = [];
            }
            parent.children.push(resource);
          });
        }
      }
    ]
  });

At this moment, we just need to assign the context menu to row headers (resources) using contextMenuResource property of the config. The context menu can be activated by right-clicking the row header.

  config: DayPilot.SchedulerConfig = {
    // ...
    contextMenuResource: this.menu
  };

This is how our SchedulerComponent looks like now:

scheduler.component.ts

import {Component, ViewChild, AfterViewInit, ChangeDetectorRef} 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[] = [];

  menu: DayPilot.Menu = new DayPilot.Menu({
    items: [
      {
        text: "Edit...",
        onClick: async args => {
          const form = [
            {name: "Resource name", id: "name"}
          ];
          const row = args.source;
          const data = row.data;
          const modal = await DayPilot.Modal.form(form, data);
          if (modal.canceled) {
            return;
          }
          const name = modal.result.name;
          this.ds.updateResource(row, name).subscribe(result => {
            row.data.name = name;
          });
        }
      },
      {
        text: "Delete",
        onClick: args => {
          let row = args.source;
          this.ds.deleteResource(row).subscribe(result => {
            row.remove();
            this.scheduler.control.message("Deleted");
          });
        }
      },
      {
        text: "-"
      },
      {
        text: "Add Child...",
        onClick: async args => {
          const form = [
            {name: "Resource name", id: "name"}
          ];
          const row = args.source;
          const data = {};
          const modal = await DayPilot.Modal.form(form, data);
          if (modal.canceled) {
            return;
          }
          const name = modal.result.name;
          this.ds.createResource(row, name).subscribe(resource => {

            // add the new resource to the tree
            let parent = row.data;
            parent.expanded = true;
            if (!parent.children) {
              parent.children = [];
            }
            parent.children.push(resource);
          });
        }
      }
    ]
  });


  config: DayPilot.SchedulerConfig = {
    timeHeaders : [
      {groupBy: "Month", format: "MMMM yyyy"},
      {groupBy: "Day", format: "d"}
    ],
    eventHeight: 40,
    scale: "Day",
    days: 31,
    startDate: "2023-01-01",
    treeEnabled: true,
    // ...
    contextMenuResource: this.menu
  };


  // ...

}

How to Display a Context Menu Hint Icon

angular scheduler resource active area icon

In addition to right-click activation, we also add a hover icon that provides a hint that a context menu is available. We will add an icon in the upper-right corner of the row header. The icon is created using a customizable object called an active area.

The active area uses a chevron SVG icon (daypilot.svg#minichevron-down-2) from the DayPilot Icons collection (see the assetsdirectory).

  config: DayPilot.SchedulerConfig = {
    // ...
    onBeforeRowHeaderRender: args => {
      args.row.areas = [
        {
          right: 2,
          top: 2,
          width: 14,
          height: 14,
          symbol: "assets/daypilot.svg#minichevron-down-2",
          style: "font-size: 12px; background-color: #f9f9f9; border: 1px solid #ccc; padding: 2px; cursor:pointer",
          visibility: "Hover",
          action: "ContextMenu"
        }
      ];
    },
  };

scheduler.component.ts

import {Component, ViewChild, AfterViewInit, ChangeDetectorRef} 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[] = [];

  menu: DayPilot.Menu = new DayPilot.Menu({
    // ...
  });


  config: DayPilot.SchedulerConfig = {
    timeHeaders : [
      {groupBy: "Month", format: "MMMM yyyy"},
      {groupBy: "Day", format: "d"}
    ],
    eventHeight: 40,
    scale: "Day",
    days: 31,
    startDate: "2023-01-01",
    treeEnabled: true,
    // ...
    contextMenuResource: this.menu,
    onBeforeRowHeaderRender: args => {
      args.row.areas = [
        {
          right: 2,
          top: 2,
          width: 14,
          height: 14,
          symbol: "assets/daypilot.svg#minichevron-down-2",
          style: "font-size: 12px; background-color: #f9f9f9; border: 1px solid #ccc; padding: 2px; cursor:pointer",
          visibility: "Hover",
          action: "ContextMenu"
        }
      ];
    },
  };


  // ...

}

How to Create Resource Groups at the Top Level

angular scheduler resource management new group

The Scheduler has a built-in helper for adding new resources at the top level. You can activate it using rowCreateHandling property of the config:

  config: DayPilot.SchedulerConfig = {
    // ...
    rowCreateHandling: "Enabled",
  };

When in-line row creating is enabled, the Scheduler will display an extra row at the bottom. After clicking the row header the user can enter a name of the new row:

angular scheduler resource management group name

After a user confirms the new name using <enter>, the Scheduler fires onRowCreate event handler. We will use this event handler to notify the server-side backend.

scheduler.component.ts

import {Component, ViewChild, AfterViewInit, ChangeDetectorRef} 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 = {
    // ...

    rowCreateHandling: "Enabled",
    rowCreateHtml: "New group...",
    onRowCreate: args => {
      // notify the backend; when successful, add the new resource to the config to display it
      this.ds.createResource(null, args.text).subscribe(result => {
        this.config.resources.push(result);
        this.scheduler.control.message("Created.");
      });
    },

  };

}

How to Create Child Resources

angular scheduler resource management add child

The “Add Child…” context menu item lets users add a new child resource.

  menu: DayPilot.Menu = new DayPilot.Menu({
    items: [
      // ...
      {
        text: "Add Child...",
        onClick: async args => {
          const form = [
            {name: "Resource name", id: "name"}
          ];
          const row = args.source;
          const data = {};
          const modal = await DayPilot.Modal.form(form, data);
          if (modal.canceled) {
            return;
          }
          const name = modal.result.name;
          this.ds.createResource(row, name).subscribe(resource => {

            // add the new resource to the tree
            let parent = row.data;
            parent.expanded = true;
            if (!parent.children) {
              parent.children = [];
            }
            parent.children.push(resource);
          });
        }
      }
    ]
  });

It uses DayPilot.Modal to display a modal dialog with the specified fields (resource name in this case).

angular scheduler resource management create dialog

The onClick event handler of the "Add Child...." menu items opens the modal dialog using DayPilot.Modal.form() method and passes the row object (available as args.source) as a data parameter. The args.source objects holds a DayPilot.Row object with row details.

How to Delete Resources

angular scheduler resource management delete

In our example, we use a context menu item to delete a resource:

  menu: DayPilot.Menu = new DayPilot.Menu({
    items: [
      // ...

      {text: "Delete", onClick: args => {
        let row = args.source;
        this.ds.deleteResource(row).subscribe(result => {
          row.remove();
          this.scheduler.control.message("Deleted");
        });
      } },

      // ...
    ]
  });

The onClick event handler calls the DataService class. On success, it removes the row from the Scheduler using DayPilot.Row.remove() method.

How to Move Rows using Drag and Drop

angular scheduler resource management move drag drop

The Scheduler supports drag and drop row moving. This feature is disabled by default - you need to enable it using rowMoveHandling property of the config:

  config: DayPilot.SchedulerConfig = {

    // ...
  
    rowMoveHandling: "Update",

    // ...

  };

When you enable drag and drop row moving the row will display a move handle on hover. Users can drag the row by the handle and move it to a new location. The source row remains highlighted during dragging.

angular scheduler resource management move target

As soon as a user drops the row at the target location onRowMoved event handler is fired. The event args object holds the operation details:

  • args.source - DayPilot.Row object with source row

  • args.target - the target DayPilot.Row object

  • args.position - drop position relative to target ("child" |  "before" | "after")

import {Component, ViewChild, AfterViewInit, ChangeDetectorRef} 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 = {

    // ...
  
    rowMoveHandling: "Update",
    onRowMoved: args => {
      this.ds.moveResource(args.source, args.target, args.position).subscribe(result => this.scheduler.control.message("Moved"));
    },
    
    // ...

  };


}