Features

  • Angular Scheduler component with resources on the Y (vertical) axis and time on the X (horizontal) axis

  • Instant event filtering: by text, category and event duration

  • Includes a trial version of DayPilot Pro for JavaScript

This tutorial doesn't include Angular Scheduler basics which are covered in the following 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.

Event Filtering by Text

angular scheduler event filtering text

As the first step, we will add a text input box which lets users filter events by content. The search box will be displayed above the Scheduler control. The <input> element specifies the source using [ngModel] - you can use it to set the initial value. It's also required for ngModelChange event handler - without [ngModel] the event won't fire.

Filter: <input type="text" [ngModel]="filter.text" (ngModelChange)="changeText($event)" /> 

The event handler fires changeText() method whenever the <input> content is updated. This method stores the new value in the filter object (as filter.text) and requests a Scheduler refresh using events.filter() call.

export class SchedulerComponent implements AfterViewInit {

  filter: DayPilot.SchedulerConfig = {
    text: ""
  };
  
  // ...

  changeText(val: string) {
    this.filter.text = val;
    this.applyFilter();
  }

  applyFilter() {
    this.scheduler.control.events.filter(this.filter);
  }

}

Whenever the events.filter() method is called the Scheduler calls onEventFilter event handler for every event to decide whether it meets the filtering condition. You need to provide custom onEventFilter handler.

Our implementation converts both the supplied text (args.filter.text) and the event text (args.e.text()) to lowercase to provide case-insensitive search. If the event text doesn't include the search text it is marked as not visible (args.visible).

export class SchedulerComponent implements AfterViewInit {

  config: DayPilot.SchedulerConfig = {
    // ...
    onEventFilter: args => {
      const params = args.filterParam;
      if (params.text && args.e.text().toLowerCase().indexOf(params.text.toLowerCase()) < 0) {
        args.visible = false;
      }
    }
  };

  // ...

}

All elements integrated (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: `
  <div class="space">
    Filter: <input type="text" [ngModel]="filter.text" (ngModelChange)="changeText($event)" /> 
    ...
  </div>
  
  <daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler>  
`,
  styles: [``]
})
export class SchedulerComponent implements AfterViewInit {

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

  events: any[] = [];

  filter: any = {
    text: ""
  };

  config: DayPilot.SchedulerConfig = {
    // ...
    onEventFilter: args => {
      const params = args.filterParam;
      if (params.text && args.e.text().toLowerCase().indexOf(params.text.toLowerCase()) < 0) {
        args.visible = false;
      }
    }
  };

  // ...

  changeText(val: string) {
    this.filter.text = val;
    this.applyFilter();
  }

  applyFilter() {
    this.scheduler.control.events.filter(this.filter);
  }

}

Event Filtering by Category

angular scheduler event filtering category

A similar approach is used for filtering by category.

The possible category values are displayed using a drop-down list (<select> element). Every dropdown change triggers changeCategory() method which updates the filter object and requests a Scheduler refresh. 

The onEventFilter event handler compares the event category (available as args.e.data.category) with the selected value. If they don't match (and the selected value isn't "any") the event is hidden using args.visible = false.

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

@Component({
  selector: 'scheduler-component',
  template: `
  <div class="space">
    ...
    Category: <select (ngModelChange)="changeCategory($event)" [ngModel]="filter.category">
      <option *ngFor="let cat of categories" [ngValue]="cat.id">{{cat.name}}</option>
    </select>
    ...
  </div>
  
  <daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler>  
`,
  styles: [``]
})
export class SchedulerComponent implements AfterViewInit {

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

  events: any[] = [];

  categories: any[] = [
    { id: "any", name: "Any"},
    { id: "cat1", name: "Marketing"},
    { id: "cat2", name: "Development"},
    { id: "cat3", name: "Sales"}
  ];

  filter: any = {
    category: "any"
  };

  config: DayPilot.SchedulerConfig = {
    // ...
    onEventFilter: args => {
      const params = args.filterParam;
      if (params.category !== "any" && args.e.data.category !== params.category) {
        args.visible = false;
      }
    }
  };

  changeCategory(val: string) {
    this.filter.category = val;
    this.applyFilter();
  }

  applyFilter() {
    this.scheduler.control.events.filter(this.filter);
  }
  
  // ...

}

The event category is provided in the event data object in category field. This is a sample data set:

events: any[] = [
  {
    id: "1",
    resource: "R1",
    start: "2022-07-03",
    end: "2022-07-10",
    text: "Prepare Web Site",
    category: "cat1"
  },
  {
    id: "2",
    resource: "R2",
    start: "2022-07-02",
    end: "2022-07-07",
    text: "Implement New Features",
    category: "cat2"
  },
  {
    id: "3",
    resource: "R2",
    start: "2022-07-08",
    end: "2022-07-10",
    text: "Launch",
    category: "cat1"
  },
  {
    id: "4",
    resource: "R1",
    start: "2022-07-11",
    end: "2022-07-15",
    text: "Contact Prospects",
    category: "cat3"
  }
];

Event Filtering by Duration

angular scheduler event filtering duration

The event duration filter checks whether the event takes more than 2 days by comparing args.e.duration() with DayPilot.Duration.ofDays(2). Events that take longer are hidden.

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

@Component({
  selector: 'scheduler-component',
  template: `
  <div class="space">
    ...
    <label for="shortonly"><input type="checkbox" id="shortonly" [ngModel]="filter.shortOnly" (ngModelChange)="changeShort($event)"> Short Events Only</label>
    ...
  </div>
  
  <daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler>  
`,
  styles: [``]
})
export class SchedulerComponent implements AfterViewInit {

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

  events: any[] = [];

  filter: any = {
    shortOnly: false
  };

  config: DayPilot.SchedulerConfig = {
    // ...
    onEventFilter: args => {
      const params = args.filterParam;
      if (params.shortOnly && args.e.duration() > DayPilot.Duration.ofDays(2)) {
        args.visible = false;
      }
    }
  };

  changeShort(val: string) {
    this.filter.shortOnly = val;
    this.applyFilter();
  }

  applyFilter() {
    this.scheduler.control.events.filter(this.filter);
  }
  
  // ...

}

All Filters Integrated (and Filter Clearing)

angular scheduler event filtering all filters

Finally, we integrate all three filter types. In order to be visible in the Scheduler, the event must match all three conditions (text, category, duration).

We'll also add a "Clear" button which resets all filter parameters and displays all events.

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

@Component({
  selector: 'scheduler-component',
  template: `
  <div class="toolbar">
    <span class="toolbar-item">
      Filter: <input type="text" [ngModel]="filter.text" (ngModelChange)="changeText($event)" />
    </span>
    <span class="toolbar-item">
        Category:
      <select (ngModelChange)="changeCategory($event)" [ngModel]="filter.category">
        <option *ngFor="let cat of categories" [ngValue]="cat.id">{{cat.name}}</option>
        </select>
    </span>
    <span class="toolbar-item">
        <label for="shortonly"><input type="checkbox" id="shortonly" [ngModel]="filter.shortOnly" (ngModelChange)="changeShort($event)"> Short Events Only</label>
    </span>
    <span class="toolbar-item">
    <a href="" (click)="clearFilter()">Clear</a>
    </span>
  </div>

  <daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler>
`,
  styles: [`
    .toolbar {
      margin: 10px 0px;
      font-size: 14px;
    }
    .toolbar input, .toolbar select {
      font-size: 14px;
      padding: 2px;
    }
    .toolbar-item {
      display: inline-block;
      margin: 0px 5px;
    }

  `]
})
export class SchedulerComponent implements AfterViewInit {

  @ViewChild("scheduler") scheduler: DayPilotSchedulerComponent;

  events: any[] = [];

  categories: any[] = [
    { id: "any", name: "Any"},
    { id: "cat1", name: "Marketing"},
    { id: "cat2", name: "Development"},
    { id: "cat3", name: "Sales"}
  ];

  filter: any = {
    category: "any",
    text: "",
    shortOnly: false
  };

  config: DayPilot.SchedulerConfig = {
    timeHeaders : [
      {groupBy: "Month", format: "MMMM yyyy"},
      {groupBy: "Day", format: "d"}
    ],
    eventHeight: 60,
    scale: "Day",
    days: 31,
    startDate: "2022-07-01",
    onBeforeEventRender: args => {
      const categoryId = (<any>args.data).category;
      switch (categoryId) {
        case "cat1":
          args.data.barColor = "#45818e";
          break;
        case "cat2":
          args.data.barColor = "#f1c232";
          break;
        case "cat3":
          args.data.barColor = "#6aa84f";
          break;
      }
      const category = this.categories.find(c => c.id == categoryId);
      if (category) {
        args.data.areas = [
          { bottom: 5, left: 3, html: category.name, style: "color: " + args.data.barColor}
        ];
      }
    },
    onEventFilter: args => {
      const params = args.filterParam;
      if (params.text && args.e.text().toLowerCase().indexOf(params.text.toLowerCase()) < 0) {
        args.visible = false;
      }
      if (params.category !== "any" && args.e.data.category !== params.category) {
        args.visible = false;
      }
      if (params.shortOnly && args.e.duration() > DayPilot.Duration.ofDays(2)) {
        args.visible = false;
      }
    }
  };

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

  changeText(val: string) {
    this.filter.text = val;
    this.applyFilter();
  }

  changeCategory(val: string) {
    this.filter.category = val;
    this.applyFilter();
  }

  changeShort(val: string) {
    this.filter.shortOnly = val;
    this.applyFilter();
  }

  clearFilter() {
    this.filter.category = "any";
    this.filter.text = "";
    this.filter.shortOnly = false;
    this.applyFilter();
    return false;
  }

  applyFilter() {
    this.scheduler.control.events.filter(this.filter);
  }

}