48

React Scheduler Component Tutorial | DayPilot Code

 3 years ago
source link: https://code.daypilot.org/77607/react-scheduler-tutorial
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

What you will learn in this tutorial

  • How to add a React Scheduler component to your application

  • How to display monthly and weekly scheduler and switch between the views using zoom

  • How to load reservation data

  • How to change event/reservation color

  • How to enable drag and drop operations (moving, resizing)

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

How to start using the React Scheduler immediately?

Use the online Scheduler configurator. The React Scheduler component is easy to configure and it can be added to your application in a few steps that are described below.

However, you can skip the setup phase and use the online Scheduler UI Builder application to configure the React Scheduler component and download a  ready-to-run project.

How to install and configure the React Scheduler component

We assume that you already have some experience with React and that you have the environment ready (Node.js and NPM are required).

Create a new React project

The first step is to create a new React project using create-react-app:

npx create-react-app reach-scheduler

Install the DayPilot Pro package

In order to use DayPilot Pro in your React project it's necessary to install the DayPilot Pro React module.

You can install it using npm:

npm install https://npm.daypilot.org/daypilot-pro-react/trial/2021.2.4990.tar.gz

Or using yarn:

yarn add https://npm.daypilot.org/daypilot-pro-react/trial/2021.2.4990.tar.gz

You can get the latest version at npm.daypilot.org.

Add the React Scheduler component

The daypilot-pro-react module includes a DayPilotScheduler class which is a React component that provides access to DayPilot Scheduler JavaScript component using React API.

You can add the Scheduler to your React application using <DayPilotScheduler> tag:

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

class Scheduler extends Component {
    render() {
        return (
            <DayPilotScheduler />
        );
    }
}

export default Scheduler;

This will display an empty Scheduler component with the default configuration - without any resources or events/reservation:

react-scheduler-component-initialization.png

How to add rows to the Scheduler

In order to display any useful data, we need to define rows using resources property:

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

class Scheduler extends Component {
    render() {
        return (
                <DayPilotScheduler
                    resources = {[
                    {name: "Resource A", id: "A"},
                    {name: "Resource B", id: "B"},
                    {name: "Resource C", id: "C"},
                    {name: "Resource D", id: "D"},
                    {name: "Resource E", id: "E"},
                    {name: "Resource F", id: "F"},
                    {name: "Resource G", id: "G"}
                ]}
            />
        );
    }
}

export default Scheduler;

react-scheduler-component-resources.png

The React Scheduler component supports all properties from the JavaScript Scheduler API. We will add a couple more options to customize the Scheduler appearance.

In the default configuration, the time header of the Scheduler displays today (one cell per hour). We will use startDatedaysscale and timeHeaders properties to customize :

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

class Scheduler extends Component {
    render() {
        return (
            <DayPilotScheduler
                startDate = {DayPilot.Date.today().firstDayOfMonth()}
                days = {DayPilot.Date.today().daysInMonth()}
                scale = {"Day"}
                timeHeaders = {[
                    { groupBy: "Month"},
                    { groupBy: "Day", format: "d"}
                ]}
                resources = {[
                    {name: "Resource A", id: "A"},
                    {name: "Resource B", id: "B"},
                    {name: "Resource C", id: "C"},
                    {name: "Resource D", id: "D"},
                    {name: "Resource E", id: "E"},
                    {name: "Resource F", id: "F"},
                    {name: "Resource G", id: "G"}
                ]}
            />
        );
    }
}

export default Scheduler;

Our React Scheduler component now displays the current month, one cell per day:

react-scheduler-component-timeline-time-headers.png

We can also adjust the grid size using cellWidth and eventHeight properties of the React scheduler component:

<DayPilotScheduler
  cellWidth = {50}
  eventHeight = {35}
  //...
/>

react-scheduler-component-grid-size.png

The cellWidth property defines the Scheduler grid cell width in pixels. 

The eventHeight property specifies the height of event boxes. There are no events in our Scheduler yet but the height of the Scheduler rows is automatically adjusted to match the event height.

How to load Scheduler reservations

react-scheduler-component-loading-events.png

So far, we have defined the Scheduler properties inline by specifying the values directly:

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

class Scheduler extends Component {
    render() {
        return (
            <DayPilotScheduler
              startDate = {DayPilot.Date.today().firstDayOfMonth()}
              days = {DayPilot.Date.today().daysInMonth()}
              scale = {"Day"}
              // ...
            />
        );
    }
}

export default Scheduler;

However, some properties may need to be changed dynamically so we will define them using state instead:

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

class Scheduler extends Component {

    constructor(props) {
        super(props);

        this.state = {
            startDate: "2018-05-01",
            days: 31,
            scale: "Day",
            // ...
        };
    }

    render() {
        return (
            <DayPilotScheduler
                startDate = {this.state.startDate}
                days = {this.state.days}
                scale = {this.state.scale}
                // ...
            />
        );
    }
}

export default Scheduler;

This will allow us to update the Scheduler appearance and data by simply changing the right properties of the state object. The Scheduler component will detect the change and update itself.

The list of properties loaded from state can get long and the properties assignment code is repetitive. Fortunately, ES6 supports the destructuring assignment expression which will let us assign all properties at once:

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

class Scheduler extends Component {

    constructor(props) {
        super(props);

        this.state = {
            startDate: "2021-06-01",
            days: 31,
            scale: "Day",
            // ...
        };
    }

    render() {
        const {...config} = this.state;
        return (
            <DayPilotScheduler
                {...config}
                // ...
            />
        );
    }
}

export default Scheduler;

In order to load events, we will define an array with event records and use the reference for the events property:

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

class Scheduler extends Component {

    constructor(props) {
        super(props);

        this.state = {
            startDate: "2021-10-01",
            days: 31,
            scale: "Day",
            eventHeight:30,
            cellWidth: 50,
            timeHeaders: [

                { groupBy: "Month"},
                { groupBy: "Day", format: "d"}

            ],
            cellWidthSpec: "Auto",
            resources: [
                {name: "Resource A", id: "A"},
                {name: "Resource B", id: "B"},
                {name: "Resource C", id: "C"},
                {name: "Resource D", id: "D"},
                {name: "Resource E", id: "E"},
                {name: "Resource F", id: "F"},
                {name: "Resource G", id: "G"}
            ],
            events: [
                {id: 1, text: "Event 1", start: "2021-10-02T00:00:00", end: "2021-10-05T00:00:00", resource: "A" },
                {id: 2, text: "Event 2", start: "2021-10-03T00:00:00", end: "2021-10-10T00:00:00", resource: "C", barColor: "#38761d", barBackColor: "#93c47d" },
                {id: 3, text: "Event 3", start: "2021-10-02T00:00:00", end: "2021-10-08T00:00:00", resource: "D", barColor: "#f1c232", barBackColor: "#f1c232" },
                {id: 4, text: "Event 3", start: "2021-10-02T00:00:00", end: "2021-10-08T00:00:00", resource: "E", barColor: "#cc0000", barBackColor: "#ea9999" }
            ]
        };
    }

    render() {
        const {...config} = this.state;
        return (
            <div>
                <DayPilotScheduler
                    {...config}
                />
            </div>
        );
    }
}

export default Scheduler;

The items of the events array need to follow the structure defined for DayPilot.Event.data property.

How to switch views using React Scheduler zoom

react-scheduler-component-zoom.png

Now that we have defined the Scheduler properties dynamically we can use the React automatic change detection mechanism to change the Scheduler appearance dynamically.

Let's create a simple Zoom component using radio buttons that will let users choose the desired zoom level of the Scheduler:

scheduler/Zoom.js

import React, {Component} from 'react';

class Zoom extends Component {

    constructor(props) {
        super(props);
        this.state = {
            level: "month"
        }
    }

    change(ev) {
        const newLevel = ev.target.value;

        this.setState({
            level: newLevel
        });

        if (this.props.onChange) {
            this.props.onChange({level: newLevel})
        }

    }

    render() {
        return (
            <span className="toolbar-item">
                Zoom:
                <label><input type="radio" name="zoom" value="month" onChange={ev => this.change(ev)} checked={this.state.level === "month"} /> Month</label>
                <label><input type="radio" name="zoom" value="week"  onChange={ev => this.change(ev)} checked={this.state.level === "week"} /> Week</label>
            </span>
        );
    }
}

export default Zoom;

The Zoom component displays radio buttons group with two zoom level options:

  • Month

Whenever the user selects a new value the internal component state is updated and the parent component is notified using a custom onChange callback. The onChange argument holds the newly selected zoom level:

  • args.level ("month" | "week")

Now we can add our new Zoom component to our application. We will listen to zoom level changes by defining an onChange event handler. Our event handler updates the required properties of the Scheduler state object. The Scheduler component detects a state change and updates automatically.

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

class Scheduler extends Component {

    constructor(props) {
        super(props);

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

    zoomChange(args) {
        switch (args.level) {
            case "month":
                this.setState({
                    startDate: DayPilot.Date.today().firstDayOfMonth(),
                    days: DayPilot.Date.today().daysInMonth(),
                    scale: "Day"
                });
                break;
            case "week":
                this.setState({
                    startDate: DayPilot.Date.today().firstDayOfWeek(),
                    days: 7,
                    scale: "Day"
                });
                break;
            default:
                throw new Error("Invalid zoom level");
        }
    }

    render() {
        const {...config} = this.state;
        return (
            <div>

                <div className="toolbar">
                  <Zoom onChange={args => this.zoomChange(args)} />
                </div>

                <DayPilotScheduler
                    {...config}
                />
            </div>
        );
    }
}

export default Scheduler;

How to handle user events (drag and drop)

react-scheduler-component-event-moving-drag-and-drop.png

The Scheduler defines event handlers that you can use to get notifications of user changes. Let's see how it works for drag and drop event moving.

Simply define a callback method that receives an args object as a parameter. The args object holds details about the drag and drop action and you can use it to notify the server about the change:

<DayPilotScheduler
    // ...
    onEventMoved={args => {
      console.log("Event moved: ", args.e.data.id, args.newStart, args.newEnd, args.newResource);
    }}
/>

How to resize Scheduler events

react-scheduler-component-drag-and-drop-resizing.png

You can handle drag and drop event resizing using the same mechanism. The Scheduler events/reservations can be resized by dragging either the start or end. As soon as the user finishes the resizing the Scheduler fires onEventResized event.

The resizing is enabled by default (eventResizeHandling is set to "Update") and all we need to do is to add a new onEventResized event handler:

<DayPilotScheduler
  // ...
  onEventResized={args => {
      console.log("Event resized: ", args.e.data.id, args.newStart, args.newEnd);
      this.scheduler.message("Event resized: " + args.e.data.text);
  }}
/>

How to add new events/reservations

react-scheduler-component-creating-events.png

To add support for creating new events, we need to handle onTimeRangeSelected event handler:

<DayPilotScheduler
  // ...
  onTimeRangeSelected={args => {
    DayPilot.Modal.prompt("New event name", "Event").then(modal => {
      this.scheduler.clearSelection();
      if (!modal.result) {
        return;
      }
      this.scheduler.events.add({
        id: DayPilot.guid(),
        text: modal.result,
        start: args.start,
        end: args.end,
        resource: args.resource
      });
    });
  }}
  ref={component => { this.scheduler = component && component.control; }}
/>

The event handler will open a simple prompt using the built-in DayPilot.Modal dialog and ask for the event name. When the user hits OK, we create the event using the direct API - events.add() method.

react-scheduler-component-new-event-modal-dialog.png

How to display a message to the user using React Scheduler API

react-scheduler-component-message-direct-api.png

The Scheduler component offers a rich API which you can use in your application. The Scheduler methods let you use advanced features that can't be controlled by properties.

The following example uses message() method to display a custom message at the top of the Scheduler. It fades away after a couple of seconds and it can be used to provide feedback to users.

Before we can call the Scheduler methods, we need to get a reference to the DayPilot.Scheduler object. It's available as control property of the DayPilotScheduler React component. We will store the reference as scheduler property of our control using ref attribute:

<DayPilotScheduler
    // ...    
    ref={component => { this.scheduler = component && component.control; }}
/>

Now we can use this.scheduler to invoke the Scheduler methods directly:

<DayPilotScheduler
    // ...
    onEventMoved={args => this.scheduler.message("Event moved: " + args.e.data.text)}
    // ...
/>

Can you change the color of reservations?

react-scheduler-component-tutorial-reservation-color.png

The React Scheduler component is very customizable and it lets you change the event color easily.

By default, the events are white and display a colored bar at the top. If you want to hide the bar, you need to add durationBarVisible property to the config:

this.state = {
  durationBarVisible: false,
  // ...
}

To change the event color depending on the event type, you need to use onBeforeEventRender event handler. This event is fired for every event/reservation when it is loaded.

onBeforeEventRender: args => {
  if (!args.data.backColor) {
    args.data.backColor = "#93c47d";
  }
  args.data.borderColor = "darker";
  args.data.fontColor = "white";
}

The "darker" value of the borderColor property is a built-in helper that will calculate a darker color from backColor value automatically.

Full Source Code of Scheduler.js

scheduler/Scheduler.js

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

class Scheduler extends Component {

  constructor(props) {
    super(props);

    this.state = {
      startDate: "2021-06-01",
      days: 31,
      scale: "Day",
      timeHeaders: [
        {groupBy: "Month"},
        {groupBy: "Day", format: "d"}
      ],
      cellWidthSpec: "Auto",
      cellWidth: 50,
      durationBarVisible: false,
      treeEnabled: true,
      rowHeaderColumns: [
        {name: "Car"},
        {name: "Seats", display: "seats", width: 50},
        {name: "Doors", display: "doors", width: 50},
        {name: "Transmission", display: "transmission", width: 90},
      ],
      resources: [
        {
          name: "Convertible", id: "G2", expanded: true, children: [
            {name: "MINI Cooper", seats: 4, doors: 2, transmission: "Automatic", id: "A"},
            {name: "BMW Z4", seats: 4, doors: 2, transmission: "Automatic", id: "B"},
            {name: "Ford Mustang", seats: 4, doors: 2, transmission: "Automatic", id: "C"},
            {name: "Mercedes-Benz SL", seats: 2, doors: 2, transmission: "Automatic", id: "D"},
          ]
        },
        {
          name: "SUV", id: "G1", expanded: true, children: [
            {name: "BMW X1", seats: 5, doors: 4, transmission: "Automatic", id: "E"},
            {name: "Jeep Wrangler", seats: 5, doors: 4, transmission: "Automatic", id: "F"},
            {name: "Range Rover", seats: 5, doors: 4, transmission: "Automatic", id: "G"},
          ]
        },
      ],
      events: [
        {id: 101, text: "Reservation 101", start: "2021-06-02T00:00:00", end: "2021-06-05T00:00:00", resource: "A"},
        {id: 102, text: "Reservation 102", start: "2021-06-06T00:00:00", end: "2021-06-10T00:00:00", resource: "A"},
        {
          id: 103,
          text: "Reservation 103",
          start: "2021-06-03T00:00:00",
          end: "2021-06-10T00:00:00",
          resource: "C",
          backColor: "#6fa8dc",
          locked: true
        },
        {
          id: 104,
          text: "Reservation 104",
          start: "2021-06-02T00:00:00",
          end: "2021-06-08T00:00:00",
          resource: "E",
          backColor: "#f6b26b",
          plus: true
        },
        {
          id: 105,
          text: "Reservation 105",
          start: "2021-06-03T00:00:00",
          end: "2021-06-09T00:00:00",
          resource: "G",
        },
        {
          id: 106,
          text: "Reservation 106",
          start: "2021-06-02T00:00:00",
          end: "2021-06-07T00:00:00",
          resource: "B",
        }
      ],
      onBeforeEventRender: args => {
        if (!args.data.backColor) {
          args.data.backColor = "#93c47d";
        }
        args.data.borderColor = "darker";
        args.data.fontColor = "white";

        args.data.areas = [];
        if (args.data.locked) {
          args.data.areas.push(
            {
              right: 4,
              top: 8,
              height: 18,
              width: 18,
              symbol: "icons/daypilot.svg#padlock",
              fontColor: "white"
            }
          );
        }
        else if (args.data.plus) {
          args.data.areas.push(
            {
              right: 4,
              top: 8,
              height: 18,
              width: 18,
              symbol: "icons/daypilot.svg#plus-4",
              fontColor: "white"
            }
          );
        }
      },
    };
  }

  zoomChange(args) {
    switch (args.level) {
      case "month":
        this.setState({
          startDate: new DayPilot.Date("2021-06-01").firstDayOfMonth(),
          days: new DayPilot.Date("2021-06-01").daysInMonth(),
          scale: "Day"
        });
        break;
      case "week":
        this.setState({
          startDate: new DayPilot.Date("2021-06-01").firstDayOfWeek(),
          days: 7,
          scale: "Day"
        });
        break;
      default:
        throw new Error("Invalid zoom level");
    }
  }

  cellWidthChange(ev) {
    const checked = ev.target.checked;
    this.setState({
      cellWidthSpec: checked ? "Auto" : "Fixed"
    });
  }

  render() {
    const {...config} = this.state;
    return (
      <div>

        <div className="toolbar">
          <Zoom onChange={args => this.zoomChange(args)}/>
          <button onClick={ev => this.scheduler.message("Welcome!")}>Welcome!</button>
          <span className="toolbar-item"><label><input type="checkbox" checked={this.state.cellWidthSpec === "Auto"}
                                                       onChange={ev => this.cellWidthChange(ev)}/> Auto width</label></span>
        </div>

        <DayPilotScheduler
          {...config}
          onEventMoved={args => {
            console.log("Event moved: ", args.e.data.id, args.newStart, args.newEnd, args.newResource);
            this.scheduler.message("Event moved: " + args.e.data.text);
          }}
          onEventResized={args => {
            console.log("Event resized: ", args.e.data.id, args.newStart, args.newEnd);
            this.scheduler.message("Event resized: " + args.e.data.text);
          }}
          onTimeRangeSelected={args => {
            DayPilot.Modal.prompt("New event name", "Event").then(modal => {
              this.scheduler.clearSelection();
              if (!modal.result) {
                return;
              }
              this.scheduler.events.add({
                id: DayPilot.guid(),
                text: modal.result,
                start: args.start,
                end: args.end,
                resource: args.resource
              });
            });
          }}
          ref={component => {
            this.scheduler = component && component.control;
          }}
        />
      </div>
    );
  }
}

export default Scheduler;

Main React Application Class

App.js

import React, {Component} from 'react';
import './App.css';
import Scheduler from "./scheduler/Scheduler";

class App extends Component {
  render() {
    return (
        <Scheduler/>
    );
  }
}

export default App;

History

  • June 21, 2021: Row columns added, how to change event color

  • May 20, 2021: Upgraded to React 17, DayPilot Pro for JavaScript 2021.2.4990

  • November 22, 2020: Upgraded to DayPilot Pro for JavaScript 2020.4.4766

  • September 11, 2019: Upgraded to DayPilot Pro for JavaScript 2019.3.4000, toolbar styling, event creating, event resizing.

  • May 21, 2018: Component interop (zoom component), custom event bar color

  • May 18, 2018: Initial release


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK