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

  • Loading resources from the server-side backend

  • Row header context menu

  • Context menu hint (active area icon)

  • Creating top-level resources (groups) using an inline edit box

  • Creating child resources using a context menu and modal dialog

  • Deleting resources using context menu

  • Moving resources using drag and drop

  • Includes a trial version of DayPilot Pro for JavaScript (see License below)

This tutorial focuses on managing resources (rows). For an introduction to the 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. Buy a license.

Live Demo

You can test this project in a live demo:

Loading Resources

angular2 scheduler resource management loading

The Scheduler displays resources on the vertical axis. You can specify the resources using the "config" object ("resources" property). We will use a dummy DataService class to simulate 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 resources in a flat list. We want to uses a hierarchy of resources - that needs to be enabled using "treeEnabled" property of the config:

  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: "2021-01-01",
    treeEnabled: true,
    // ...
  };


  constructor(private ds: DataService, private cdr: ChangeDetectorRef) {}

  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;
    });
  }

}

Row Header Context Menu

angular2 scheduler resource 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 custom event handler to each of the items:

  menu: DayPilot.Menu = new DayPilot.Menu({
    items: [
      {
        text: "Edit...",
        onClick: args => {
          const form = [
            {name: "Resource name", id: "name"}
          ];
          const row = args.source;
          const data = row.data;
          DayPilot.Modal.form(form, data).then(modal => {
            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: args => {
          const form = [
            {name: "Resource name", id: "name"}
          ];
          const row = args.source;
          const data = {};
          DayPilot.Modal.form(form, data).then(modal => {
            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: args => {
          const form = [
            {name: "Resource name", id: "name"}
          ];
          const row = args.source;
          const data = row.data;
          DayPilot.Modal.form(form, data).then(modal => {
            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: args => {
          const form = [
            {name: "Resource name", id: "name"}
          ];
          const row = args.source;
          const data = {};
          DayPilot.Modal.form(form, data).then(modal => {
            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: "2021-01-01",
    treeEnabled: true,
    // ...
    contextMenuResource: this.menu
  };


  // ...

}

Context Menu Hint Icon (Active Area)

angular2 scheduler resource active area

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

The active area uses one of the predefined icons (icon-triangle-down) from the DayPilot icon font (see the icons directory).

  config: DayPilot.SchedulerConfig = {
    // ...
    onBeforeRowHeaderRender: args => {
      args.row.areas = [
        {
          right: 2,
          top: 2,
          icon: "icon-triangle-down",
          style: "font-size: 12px; background-color: #f9f9f9; border: 1px solid #ccc; padding: 2px 2px 0px 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: "2021-01-01",
    treeEnabled: true,
    // ...
    contextMenuResource: this.menu,
    onBeforeRowHeaderRender: args => {
      args.row.areas = [
        {
          right: 2,
          top: 2,
          icon: "icon-triangle-down",
          style: "font-size: 12px; background-color: #f9f9f9; border: 1px solid #ccc; padding: 2px 2px 0px 2px; cursor:pointer",
          visibility: "Hover",
          action: "ContextMenu"
        }
      ];
    }
  };


  // ...

}

Creating Groups (Top-Level Resources)

angular2 scheduler resource management new group

The Scheduler has a built-in helper for creating 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:

angular2 scheduler resource management group name

After a user hits <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.");
      });
    },

  };

}

Creating Child Resources

angular2 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: args => {
          const form = [
            {name: "Resource name", id: "name"}
          ];
          const row = args.source;
          const data = {};
          DayPilot.Modal.form(form, data).then(modal => {
            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.

Deleting Resources

angular2 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.

Moving Resources using Drag and Drop

angular2 scheduler resource management move handle

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.

angular2 scheduler resource management moving

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"));
    },
    
    // ...

  };


}