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

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.

Find and Highlight Scheduler Row by Name

angular scheduler row searching tutorial typescript find first by name

Start with a generated Angular project with a pre-configured Scheduler component using the UI Builder. The Scheduler component is in the src/app/scheduler/scheduler.component.ts file.

Add a search <input>, a "Find" button, a clear button, and a counter to the Scheduler component template. The search field uses ngModel, so the standalone component imports FormsModule together with DayPilotModule.

imports: [DayPilotModule, FormsModule],
providers: [DataService],
template: ` <div class="toolbar">
    <input [(ngModel)]="search.text" placeholder="Resource name" />
    <button (click)="search.findRow()" class="find">Find</button>
    <button (click)="search.clear()" aria-label="Clear search">&times;</button>
    <span>{{ search.index }}/{{ search.total }}</span>
  </div>
  <daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler>`,

The search state and the search logic are wrapped in the search property of the SchedulerComponent class. Keeping the row-searching code in one object makes it easy to copy into another Scheduler component.

search: SearchState = {
  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 query = search.text.toLowerCase();
    const criteria = (row: DayPilot.Row) => row.name.toLowerCase().includes(query);
    const all = scheduler.rows.all().filter(criteria);
    const row = scheduler.rows.find(criteria, search.lastIndex + 1);

    if (row) {
      search.index += 1;
      search.lastIndex = row.index;
      scheduler.rows.selection.clear();
      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();
  },
};

The findRow() method is the core of the search implementation. It builds a predicate that checks the row name and then uses scheduler.rows.find() to locate a matching row.

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

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

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

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

The viewport check makes the result visible even when the matching row is currently outside the visible Scheduler rows.

Find the Next Matching Row

angular scheduler row searching tutorial typescript find next

To support the find next behavior, the search object stores the last search text and the last matched row index. When the text changes, the search starts again from the beginning and clears the existing row selection.

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

The next search starts at search.lastIndex + 1. The same predicate is also used with scheduler.rows.all().filter() to calculate the total number of matching rows.

const all = scheduler.rows.all().filter(criteria);
const row = scheduler.rows.find(criteria, search.lastIndex + 1);

When a row is found, the current position is increased and displayed next to the buttons as {{ search.index }}/{{ search.total }}.

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

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

search.total = all.length;

The Clear Button

angular scheduler row searching tutorial typescript clear button

The clear button resets the search text, the match counters, and the highlighted row.

<button (click)="search.clear()" aria-label="Clear search">&times;</button>

The clear() method also clears the Scheduler row selection so the component returns to its initial state.

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, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { DayPilot, DayPilotModule, DayPilotSchedulerComponent } from 'daypilot-pro-angular';
import { DataService } from './data.service';

interface SearchState {
  text: string;
  lastIndex: number;
  lastText: string | null;
  index: number;
  total: number;
  findRow: () => void;
  clear: () => void;
}

@Component({
  selector: 'scheduler-component',
  standalone: true,
  imports: [DayPilotModule, FormsModule],
  providers: [DataService],
  template: ` <div class="toolbar">
      <input [(ngModel)]="search.text" placeholder="Resource name" />
      <button (click)="search.findRow()" class="find">Find</button>
      <button (click)="search.clear()" aria-label="Clear search">&times;</button>
      <span>{{ search.index }}/{{ search.total }}</span>
    </div>
    <daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler>`,
  styles: [
    `
      .toolbar {
        align-items: center;
        display: flex;
        font-size: 14px;
        gap: 5px;
        margin: 10px 0;
      }

      input {
        border: 1px solid #c0c0c0;
        padding: 6px;
      }

      button {
        background-color: #f3f3f3;
        border: 1px solid #c0c0c0;
        color: #333;
        cursor: pointer;
        padding: 6px 8px;
      }

      button.find {
        width: 100px;
      }
    `,
  ],
})
export class SchedulerComponent implements AfterViewInit {
  @ViewChild('scheduler')
  scheduler!: DayPilotSchedulerComponent;

  events = signal<DayPilot.EventData[]>([]);

  search: SearchState = {
    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 query = search.text.toLowerCase();
      const criteria = (row: DayPilot.Row) => row.name.toLowerCase().includes(query);
      const all = scheduler.rows.all().filter(criteria);
      const row = scheduler.rows.find(criteria, search.lastIndex + 1);

      if (row) {
        search.index += 1;
        search.lastIndex = row.index;
        scheduler.rows.selection.clear();
        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 = signal<DayPilot.SchedulerConfig>({
    eventClickHandling: 'Disabled',
    timeRangeSelectedHandling: 'Enabled',
    onTimeRangeSelected: async (args) => {
      const scheduler = args.control;
      const modal = await DayPilot.Modal.prompt('Create a new event:', 'Event 1');
      scheduler.clearSelection();
      if (modal.canceled) {
        return;
      }
      scheduler.events.add({
        start: args.start,
        end: args.end,
        id: DayPilot.guid(),
        resource: args.resource,
        text: modal.result,
      });
    },
    eventDeleteHandling: 'Update',
    onEventDeleted: (args) => {
      console.log('Event deleted: ' + args.e.text());
    },
    eventHoverHandling: 'Bubble',
    bubble: new DayPilot.Bubble({
      onLoad: (args) => {
        // if the event object doesn't specify "bubbleHtml" property
        // this onLoad handler will be called to provide the bubble HTML
        args.html = 'Event details';
      },
    }),
    eventMoveHandling: 'Update',
    onEventMoved: (args) => {
      console.log('Event moved: ' + args.e.text());
    },
    eventResizeHandling: 'Update',
    onEventResized: (args) => {
      console.log('Event resized: ' + args.e.text());
    },
    days: DayPilot.Date.today().daysInMonth(),
    startDate: DayPilot.Date.today().firstDayOfMonth(),
    treeEnabled: true,
    timeHeaders: [{ groupBy: 'Month' }, { format: 'd', groupBy: 'Day' }],
    scale: 'Day',
  });

  constructor(private ds: DataService) {}

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

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