Features

  • Find Angular Scheduler rows that match the specified rule and highlight them using the built-in row selection feature.
  • This example searches by name (or a part of it) but you can easily extend the logic to include other criteria.
  • Click the "Find" button again to find and highlight the next row that matches the rule.
  • The current position and the total number of matching rows are displayed next to the "Find" button.
  • Requires DayPilot Pro for JavaScript 2019.4.4114 or higher
  • 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. Buy a license.

Live Demo

Find and Highlight Scheduler Row by Name

angular-scheduler-row-searching-find-first-by-name.png

Lets start with a simple search logic that finds the first row that includes the search text in its name.

In order to avoid the project setup and Scheduler configuration overhead, we will generate a new Angular project with pre-configured Scheduler component using the UI Builder online app. The Scheduler component can be found in src/app/scheduler/scheduler.component.ts file.

Now we can add a search <input> and a "Find" button to the HTML template of SchedulerComponent class:

<input [(ngModel)]="search.text">
<button (click)="search.findRow()" class="find">Find</button>

All the search-related properties and logic will be wrapped in search property of SchedulerComponent class so you can extract it easily and use in your own project:

export class SchedulerComponent implements AfterViewInit {

  @ViewChild('scheduler', {static: false})
  scheduler: DayPilotSchedulerComponent;

  events: any[] = [];

  search = {
    text: '',
    findRow: () => {
      const scheduler = this.scheduler.control;
      const search = this.search;

      scheduler.rows.selection.clear();

      const criteria = (r) => r.name.toLowerCase().includes(search.text.toLowerCase());
      const row = scheduler.rows.find(criteria);

      if (row) {
        scheduler.rows.selection.add(row);

        if (!scheduler.getViewPort().resources.includes(row.id)) {
          scheduler.scrollToResource(row);
        }

      }
    },
  };


  config: DayPilot.SchedulerConfig = {
    timeHeaders: [{groupBy: 'Month'}, {groupBy: 'Day', format: 'd'}],
    scale: 'Day',
    days: 31,
    startDate: '2019-12-01',
    // ...
  };


  // ...

}

The search.findRow() method is the core of the search implementation.

It clears the existing row selection:

scheduler.rows.selection.clear();

Then it searches for the first row that includes the specified text:

const criteria = (r) => r.name.toLowerCase().includes(search.text.toLowerCase());
const row = scheduler.rows.find(criteria);

If found, the row is highlighted using the built-in row selecting feature of the Angular Scheduler component:

if (row) {
  scheduler.rows.selection.add(row);
}

We also need to make sure that the highlighted row is visible. The findRow() method checks the current viewport of the Scheduler and scrolls to the target row if needed:

if (row) {
  scheduler.rows.selection.add(row);

  if (!scheduler.getViewPort().resources.includes(row.id)) {
    scheduler.scrollToResource(row);
  }

}

Find the Next Matching Row

angular-scheduler-row-searching-find-next.png

In this step, we will extend the search logic to support "find next" functionality:

Updated HTML template:

<input [(ngModel)]="search.text">
<button (click)="search.findRow()" class="find">Find</button>
{{search.index}}/{{search.total}}

Updated search object:

search = {
  text: '',
  lastIndex: -1,
  lastText: null,
  index: 0,
  total: 0,
  findRow: () => {
    const scheduler = this.scheduler.control;
    const search = this.search;

    if (search.lastText !== search.text) {
      search.lastIndex = -1;
      search.index = 0;
      scheduler.rows.selection.clear();
    }
    search.lastText = search.text;

    const criteria = (r) => r.name.toLowerCase().includes(search.text.toLowerCase());
    const all = scheduler.rows.all().filter(criteria);
    const row = scheduler.rows.find(criteria, search.lastIndex + 1);

    if (row) {
      search.index += 1;
      scheduler.rows.selection.clear();
      search.lastIndex = row.index;
      scheduler.rows.selection.add(row);

      if (!scheduler.getViewPort().resources.includes(row.id)) {
        scheduler.scrollToResource(row);
      }

    }
    search.total = all.length;
  }
};

This makes our search object more complex. In addition to the search text (text), we need to keep track of the last search text so we can detect a change and start from the beginning (lastText), the index of the row that is highlighted (lastIndex), the total number of matching rows (total) and the position of the current selection in the matching rows (index).

The findRow() method uses the existing criteria function to calculate the total number of matching rows:

const all = scheduler.rows.all().filter(criteria);
// ...
search.total = all.length;

The Clear Button

angular-scheduler-row-searching-clear-button.png

And the last thing to add is the "clear" button that resets the search text and clear the row highlighting.

HTML template:

<button (click)="search.clear()">&times;</button>

clear() method:

search = {
  // ...
  clear: () => {
    const scheduler = this.scheduler.control;
    const search = this.search;

    search.text = '';
    search.lastText = '';
    search.lastIndex = -1;
    search.index = 0;
    search.total = 0;

    scheduler.rows.selection.clear();
  }
};

Full Source Code

Here is the source code of the scheduler.component.ts file with the complete row searching implementation:

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="toolbar">
      <input [(ngModel)]="search.text">&nbsp;<button (click)="search.findRow()" class="find">Find</button><button (click)="search.clear()">&times;</button>&nbsp;{{search.index}}/{{search.total}}
    </div>
    <daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler>`,
  styles: [`
    .toolbar {
      font-size: 14px;
      margin: 10px 0px;
    }
    input {
      padding: 6px;
    }
    button {
      background-color: #f3f3f3;
      border: 1px solid #c0c0c0;
      color: #333;
      padding: 6px 6px;
      cursor: pointer;
      margin-right: 5px;
    }
    button.find {
      width: 100px;
    }
  `]
})
export class SchedulerComponent implements AfterViewInit {

  @ViewChild('scheduler', {static: false})
  scheduler: DayPilotSchedulerComponent;

  events: any[] = [];

  search = {
    text: '',
    lastIndex: -1,
    lastText: null,
    index: 0,
    total: 0,
    findRow: () => {
      const scheduler = this.scheduler.control;
      const search = this.search;

      if (search.lastText !== search.text) {
        search.lastIndex = -1;
        search.index = 0;
        scheduler.rows.selection.clear();
      }
      search.lastText = search.text;

      const criteria = (r) => r.name.toLowerCase().includes(search.text.toLowerCase());
      const all = scheduler.rows.all().filter(criteria);
      const row = scheduler.rows.find(criteria, search.lastIndex + 1);

      if (row) {
        search.index += 1;
        scheduler.rows.selection.clear();
        search.lastIndex = row.index;
        scheduler.rows.selection.add(row);

        if (!scheduler.getViewPort().resources.includes(row.id)) {
          scheduler.scrollToResource(row);
        }

      }
      search.total = all.length;
    },
    clear: () => {
      const scheduler = this.scheduler.control;
      const search = this.search;

      search.text = '';
      search.lastText = '';
      search.lastIndex = -1;
      search.index = 0;
      search.total = 0;

      scheduler.rows.selection.clear();
    }
  };

  config: DayPilot.SchedulerConfig = {
    timeHeaders: [{groupBy: 'Month'}, {groupBy: 'Day', format: 'd'}],
    scale: 'Day',
    days: 31,
    startDate: '2019-12-01',
    onTimeRangeSelected: (args) => {
      const dp = this.scheduler.control;
      DayPilot.Modal.prompt('Create a new event:', 'Event 1').then((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
        }));
      });
    },
    treeEnabled: true,
  };

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


}