Overview

The active areas feature of the Angular Scheduler component allows adding different types of action buttons to the Scheduler row header columns:

  • context menu that is activated using a hover icon

  • hyperlink-like button with custom text

  • custom actions icons

The project 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.

Context Menu Triggered by Active Area

angular scheduler row header actions context menu area

The first example uses a hover active area (visibility: "Hover") to show a button that activates a context menu:

angular scheduler row header actions context menu open

The active area is added to the first row header column using onBeforeHeaderRender event handler.

scheduler.component.ts

onBeforeRowHeaderRender: args=> {

  args.row.columns[0].areas = [
    {
      width: 15,
      right: 5,
      top: 5,
      height: 15,
      visibility: "Hover",
      backColor: "#fff",
      html: "...",
      action: "ContextMenu",
      contextMenu: this.rowMenu,
      style: "cursor: pointer; border: 1px solid #ccc; text-align: center;"
    }
  ];
  
  // ...

}

Context menu definition:

rowMenu: DayPilot.Menu = new DayPilot.Menu({
  items: [
    { text: "Open"},
    { text: "Delete", onClick: args => {
        let row = args.source;
        alert("Deleting: " + row.data.myData);
      }
    }
  ]
});

You can call Angular methods from onClick event handler of the menu item.

Hyperlink Button

angular scheduler row header actions cell

Another type of active area adds a custom text that is styled as a hyperlink.

The onClick event handler of the active area lets you specify the click action. You can use it to call Angular methods available in your component.

onBeforeRowHeaderRender: args=> {

  args.row.columns[1].areas = [
    {
      left: 0,
      right: 0,
      top: 0,
      bottom: 0,
      html: "Click me",
      style: "cursor: pointer; text-decoration: underline; display: flex; align-items: center; justify-content: center; color: #cc0000;",
      onClick: (clickArgs: any) => {
        let row = clickArgs.source;
        alert("You clicked a row with the following data: " + row.data.myData);
      }
    }
  ];

}

Multiple Action Icons in a Cell

angular scheduler row header actions icons

The last column displays three color icons with different onClick event handlers.

onBeforeRowHeaderRender: args=> {

  args.row.columns[1].areas = [
    {
      left: 10,
      top: 10,
      width: 15,
      height: 15,
      backColor: "#f1c232",
      // backColor: "#cc0000",
      style: "cursor: pointer; border: 1px solid #bf9000; box-sizing: border-box;",
      toolTip: "Yellow",
      onClick: (clickArgs: any) => {
        let row = clickArgs.source;
        alert("You clicked a yellow icon: " + row.data.myData);
      }
    },
    {
      left: 30,
      top: 10,
      width: 15,
      height: 15,
      backColor: "#6aa84f",
      style: "cursor: pointer; border: 1px solid #38761d; box-sizing: border-box;",
      toolTip: "Green",
      onClick: clickArgs => {
        let row = clickArgs.source;
        alert("You clicked a green icon: " + row.data.myData);
      }
    },
    {
      left: 50,
      top: 10,
      width: 15,
      height: 15,
      backColor: "#3c78d8",
      style: "cursor: pointer; border: 1px solid #1155cc; box-sizing: border-box;",
      toolTip: "Blue",
      onClick: clickArgs => {
        let row = clickArgs.source;
        alert("You clicked a blue icon: " + row.data.myData);
      }
    },
  ];

}

Full Source Code

scheduler.component.ts

import {AfterViewInit, Component, ViewChild} 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', {static: false})
  scheduler!: DayPilotSchedulerComponent;

  events: any[] = [];

  rowMenu: DayPilot.Menu = new DayPilot.Menu({
    items: [
      { text: "Open"},
      { text: "Delete", onClick: args => {
          let row = args.source;
          alert("Deleting: " + row.data.myData);
        }
      }
    ]
  });

  config: DayPilot.SchedulerConfig = {
    timeHeaders: [{"groupBy":"Month"},{"groupBy":"Day","format":"d"}],
    scale: "Day",
    days: DayPilot.Date.today().daysInMonth(),
    startDate: DayPilot.Date.today().firstDayOfMonth(),
    timeRangeSelectedHandling: "Enabled",
    onTimeRangeSelected: function (args) {
      var dp = args.control;
      DayPilot.Modal.prompt("Create a new event:", "Event 1").then(function(modal) {
        dp.clearSelection();
        if (!modal.result) { return; }
        dp.events.add(new DayPilot.Event({
          start: args.start,
          end: args.end,
          id: DayPilot.guid(),
          resource: args.resource,
          text: modal.result
        }));
      });
    },
    rowHeaderColumns: [
      { text: "Name" },
      { text: "Click" },
      { text: "Actions"}
    ],
    treeEnabled: true,
    onBeforeRowHeaderRender: args=> {

      args.row.columns[0].areas = [
        {
          width: 15,
          right: 5,
          top: 5,
          height: 15,
          visibility: "Hover",
          backColor: "#fff",
          html: "...",
          action: "ContextMenu",
          contextMenu: this.rowMenu,
          style: "cursor: pointer; border: 1px solid #ccc; text-align: center;"
        }
      ];

      args.row.columns[1].areas = [
        {
          left: 0,
          right: 0,
          top: 0,
          bottom: 0,
          html: "Click me",
          style: "cursor: pointer; text-decoration: underline; display: flex; align-items: center; color: #cc0000; box-sizing: border-box; padding: 2px;",
          onClick: (clickArgs: any) => {
            let row = clickArgs.source;
            alert("You clicked a row with the following data: " + row.data.myData);
          }
        }
      ];

      args.row.columns[2].areas = [
        {
          left: 10,
          top: 10,
          width: 15,
          height: 15,
          backColor: "#f1c232",
          // backColor: "#cc0000",
          style: "cursor: pointer; border: 1px solid #bf9000; box-sizing: border-box;",
          toolTip: "Yellow",
          onClick: (clickArgs: any) => {
            let row = clickArgs.source;
            alert("You clicked a yellow icon: " + row.data.myData);
          }
        },
        {
          left: 30,
          top: 10,
          width: 15,
          height: 15,
          backColor: "#6aa84f",
          style: "cursor: pointer; border: 1px solid #38761d; box-sizing: border-box;",
          toolTip: "Green",
          onClick: (clickArgs: any) => {
            let row = clickArgs.source;
            alert("You clicked a green icon: " + row.data.myData);
          }
        },
        {
          left: 50,
          top: 10,
          width: 15,
          height: 15,
          backColor: "#3c78d8",
          style: "cursor: pointer; border: 1px solid #1155cc; box-sizing: border-box;",
          toolTip: "Blue",
          onClick: (clickArgs: any) => {
            let row = clickArgs.source;
            alert("You clicked a blue icon: " + row.data.myData);
          }
        },
      ];

    }
  };

  constructor(private ds: DataService) {
  }

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

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

}

data.service.ts

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

@Injectable()
export class DataService {

  resources: any[] = [
    { name: 'Group A', id: 'GA', myData: "GA-value" , expanded: true, children: [
      { name: 'Resource 1', id: 'R1', myData: "R1-value" },
      { name: 'Resource 2', id: 'R2', myData: "R2-value"  }
    ]},
    { name: 'Group B', id: 'GB', myData: "GB-value" , expanded: true, children: [
      { name: 'Resource 3', id: 'R3', myData: "R3-value" , unavailable: true},
      { name: 'Resource 4', id: 'R4', myData: "R4-value" }
    ]}
  ];

  events: any[] = [
    {
      id: '1',
      resource: 'R1',
      start: '2021-05-03',
      end: '2021-05-08',
      text: 'Scheduler Event 1',
      color: '#e69138'
    },
    {
      id: '2',
      resource: 'R2',
      start: '2021-05-02',
      end: '2021-05-05',
      text: 'Scheduler Event 2',
      color: '#6aa84f'
    },
    {
      id: '3',
      resource: 'R2',
      start: '2021-05-06',
      end: '2021-05-09',
      text: 'Scheduler Event 3',
      color: '#3c78d8'
    }
  ];

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

}