Overview

  • Search events displayed by the React Scheduler component from DayPilot Pro for JavaScript.

  • Highlight the matching text in all Scheduler events.

  • Focus the event at the current position.

  • “Next” and “Previous” buttons let you move the position to the next/previous matching event.

This tutorial keeps all events visible and highlights the matching text. It is also possible to filter events and hide ones that don’t match the specified rule.

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.

How to Create the React Scheduler Component?

react scheduler how to search events initialization

First, we create a simple React component that displays the Scheduler (Scheduler.js).

  • It displays the scheduler grid using DayPilot React Scheduler component (<DayPilotScheduler>).

  • The configuration of the Scheduler is stored in the state (this.state.config).

  • It loads the data (resources and events) in componentDidMount() method using the update() method of the Scheduler.

The following code listing shows the structure of the Scheduler component (some data and configuration options are omitted to keep it short).

To learn more about the using React Scheduler component from DayPilot Pro in your React application, please see the React Scheduler Component Tutorial.

import React, {Component} from 'react';
import {DayPilot, DayPilotScheduler} from "daypilot-pro-react";

export class Scheduler extends Component {

  constructor(props) {
    super(props);

    this.state = {
      config:     {
        timeHeaders: [{groupBy: "Month"},{groupBy: "Day", format: "d"}],
        scale: "Day",
        days: 90,
        startDate: "2021-11-01",
        // ...
      }
    };
  }

  componentDidMount() {

    const resources = [
      {name: "Resource A", id: "A"},
      // ...
    ];

    const events = [
      { id: 1, text: "Event 1", start: "2021-11-02T00:00:00", end: "2021-11-05T00:00:00", resource: "A"},
      // ...
    ];

    this.scheduler.update({
      events,
      resources
    });

  }

  render() {
    return (
      <div>
        <DayPilotScheduler
          {...this.state.config}
          ref={component => this.scheduler = component && component.control}
        />
      </div>
    );
  }
}

How to Add the Search Functionality to the Scheduler?

Now we can start building the search functionality.

The search logic is wrapped in a special component (Search.js) so the content can be dynamically updated without refreshing the whole Scheduler.

<div>
  <Search scheduler={() => this.scheduler} ref={component => this.search = component} />
  <DayPilotScheduler
    {...this.state.config}
    ref={component => this.scheduler = component && component.control}
  />
</div>

react scheduler search component

The Search component (Search.js) displays four elements:

  • input box for the search phrase

  • label with the current position in the result set

  • next button

  • previous button

render() {
  return (
    <div style={{display: "flex", alignItems: "center"}}>

      Search:

      <input onChange={ev => this.search(ev)} />

      {this.info}

      <button title={"Previous"} onClick={ev => this.previous()} style={{display: this.displayStyle}} className={"next-previous"}>
        <svg width={18} height={18}><use href={"icons/daypilot.svg#minichevron-up-2"}/></svg>
      </button>

      <button title={"Next"} onClick={ev => this.next()} style={{display: this.displayStyle}} className={"next-previous"}>
        <svg width={18} height={18}><use href={"icons/daypilot.svg#minichevron-down-2"}/></svg>
      </button>

    </div>
  );
}

Note that we pass the React Scheduler component instance to our search component using scheduler property. The React Scheduler API provides methods that will help us with the implementation:

  • events.findAll() method searches the scheduler event data and returns an array of events that match the condition

  • onBeforeCellRender event lets us highlight the matching events and the current position in the results

<Search scheduler={() => this.scheduler} ... />

How to Implement the Search Logic?

The inner state of our search React component contains three variables:

  • the search phrase (phrase)

  • a list of events that match the search phrase (events)

  • the current position in the list of result (position)

Storing the search results and the current position lets us display the current position next to the input box (“1/4”) and implement the “Next” and “Previous” buttons that will let users skip between matching events.

constructor(props) {
  super(props);

  this.state = {
    position: 0,
    events: [],
    phrase: null
  };

}

You can see that we add a handler of the change event to the input box. This event is fired whenever the text box content changes.

<input onChange={ev => this.search(ev)} />

The search() method uses the search phrase (ev.target.value) to find matching events.

search(ev) {
  const phrase = ev.target.value;
  const events = this.scheduler.events.findAll(e => {
    return e.data.text.toLowerCase().includes(phrase.toLowerCase());
  });
  const position = 0;

  const e = events[position];

  this.setState({
    phrase,
    events,
    position
  }, () => {
    const events = this.scheduler.events.list;
    this.scheduler.update({events});

    this.scheduler.events.scrollIntoView(e);
  });

}

After updating the state (we store the new phrase, events, and position values there), we request an update of the Scheduler component that will highlight the new phrase.

We use an optimized update which we invoke using the direct API (update() method with a single events property in the options object). The list of events remain the same, we just read the current value (this.scheduler.events.list). This will force the Scheduler to re-render the events only.

When updating the events, the Scheduler calls the onBeforeEventRender event handler for each of them:

  • We update the event HTML (args.data.html) and highlight the search phrase in all events that contain it.

  • We add a .focused CSS class to the event at the current position (args.data.cssClass).

Here is our onBeforeEventRender implementation (you can find it in Scheduler.js):

config:     {
  onBeforeEventRender: (args) => {
    if (!this.search.phrase) {
      return;
    }

    args.data.html = this.replaceAll(args.data.text, this.search.phrase, "<span style='background-color: #ffa500; color: #fff;'>$&</span>");

    const focused = this.search.events[this.search.position];
    if (focused && focused.data.id === args.data.id) {
      args.data.cssClass = "focused";
    }
  },
  
  // ...

}

replaceAll(source, search, replace) {
  const esc = search.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
  const reg = new RegExp(esc, 'ig');
  return source.replace(reg, replace);
};

How to Add “Next” and “Previous” Buttons?

react scheduler how to search events next previous

The “Next” button skips to the next matching event.

<button title={"Next"} onClick={ev => this.next()} style={{display: this.displayStyle}} className={"next-previous"}>
  <svg width={18} height={18}><use href={"icons/daypilot.svg#minichevron-down-2"}/></svg>
</button>

The button is only displayed if the results include at least one event. Otherwise, it is hidden using CSS:

get displayStyle() {
  const hasResults = this.state.events.length > 0;
  return hasResults ? "" : "none";
}

The onClick event handler calls the next() method which advances the current position in the result set. If we are already at the last position, it skips to the first result (position = 0).

After updating the position, it refreshes the Scheduler component using the update() method.

We also make sure that the focused event is visible using the events.scrollIntoView() method.

next() {

  let position = this.state.position + 1;
  if (position > this.state.events.length - 1) {
    position = 0;
  }

  const e = this.state.events[position];

  this.setState({
    position
  }, () => {
    this.scheduler.update({events: this.scheduler.events.list});
    this.scheduler.events.scrollIntoView(e);
  });

}

The implementation of the “Previous” button is very similar:

<button title={"Previous"} onClick={ev => this.previous()} style={{display: this.displayStyle}} className={"next-previous"}>
  <svg width={18} height={18}><use href={"icons/daypilot.svg#minichevron-up-2"}/></svg>
</button>

In the previous() method, we need to check the position and skip to the last one when we hit the start.

previous() {

  let position = this.state.position - 1;
  if (position < 0) {
    position = this.state.events.length - 1;
  }

  const e = this.state.events[position];

  this.setState({
    position
  }, () => {
    this.scheduler.update({events: this.scheduler.events.list});
    this.scheduler.events.scrollIntoView(e);
  });

}