Features

  • Angular 2 Scheduler component with resources on the Y (vertical) axis and time on the X (horizontal) axis
  • Instant event filtering: by text, category and length
  • Includes a trial version of DayPilot Pro for JavaScript
  • Uses Angular CLI 

This tutorial doesn't include Angular 2 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. Buy a license.

Live Demo

You can test this project in a live demo:

Event Filtering by Text

angular-2-scheduler-event-filtering-text.png

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: any = {
    text: ""
  };
  
  // ...

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

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

}

Whenever events.filter() 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: any = {
    // ...
    onEventFilter: args => {
      var params = args.filter;
      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, ChangeDetectorRef} from "@angular/core";
import {DayPilot} 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: DayPilot.Angular.Scheduler;

  events: any[] = [];

  filter: any = {
    text: ""
  };

  config: any = {
    // ...
    onEventFilter: args => {
      var params = args.filter;
      if (params.text && args.e.text().toLowerCase().indexOf(params.text.toLowerCase()) < 0) {
        args.visible = false;
      }
    }
  };

  // ...

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

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

}

Event Filtering by Category

angular-2-scheduler-event-filtering-category.png

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, ChangeDetectorRef} from "@angular/core";
import {DayPilot} 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: DayPilot.Angular.Scheduler;

  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: any = {
    // ...
    onEventFilter: args => {
      var params = args.filter;
      if (params.category !== "any" && args.e.data.category !== params.category) {
        args.visible = false;
      }
    }
  };

  changeCategory(val) {
    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. Sample data set:

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

Event Filtering by Duration

angular-2-scheduler-event-filtering-duration.png

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

import {Component, ViewChild, AfterViewInit, ChangeDetectorRef} from "@angular/core";
import {DayPilot} 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: DayPilot.Angular.Scheduler;

  events: any[] = [];

  filter: any = {
    shortOnly: false
  };

  config: any = {
    // ...
    onEventFilter: args => {
      var params = args.filter;
      if (params.shortOnly && args.e.duration() > DayPilot.Duration.days(2)) {
        args.visible = false;
      }
    }
  };

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

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

}

All Filters Integrated (and Filter Clearing)

anguler-2-scheduler-event-filtering-all-filters.png

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 {Component, ViewChild, AfterViewInit, ChangeDetectorRef} from "@angular/core";
import {DayPilot} 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)" /> 
    Category: <select (ngModelChange)="changeCategory($event)" [ngModel]="filter.category">
      <option *ngFor="let cat of categories" [ngValue]="cat.id">{{cat.name}}</option>
    </select>
    <label for="shortonly"><input type="checkbox" id="shortonly" [ngModel]="filter.shortOnly" (ngModelChange)="changeShort($event)"> Short Events Only</label>
    <a href="" (click)="clearFilter()">Clear</a>
  </div>
  
  <daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler>  
`,
  styles: [``]
})
export class SchedulerComponent implements AfterViewInit {

  @ViewChild("scheduler") scheduler: DayPilot.Angular.Scheduler;

  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: any = {
    timeHeaders : [
      {groupBy: "Month", format: "MMMM yyyy"},
      {groupBy: "Day", format: "d"}
    ],
    eventHeight: 40,
    scale: "Day",
    days: 31,
    startDate: "2017-01-01",
    onBeforeEventRender: args => {
      switch (args.data.category) {
        case "cat1":
          args.data.barColor = "#45818e";
          break;
        case "cat2":
          args.data.barColor = "#f1c232";
          break;
        case "cat3":
          args.data.barColor = "#6aa84f";
          break;
      }
      let category = this.categories.find(c => c.id == args.data.category);
      if (category) {
        args.data.areas = [
          { bottom: 5, left: 3, html: category.name, style: "color: " + args.data.barColor}
        ];
      }
    },
    onEventFilter: args => {
      var params = args.filter;
      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.days(2)) {
        args.visible = false;
      }
    }
  };

  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;

      // this is required for getEvents() that resolves immediately (no http)
      this.cdr.detectChanges();
    });
  }

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

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

  changeShort(val) {
    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);
  }

}