Features

  • Add a React Scheduler component to your application
  • The Scheduler component displays a timeline for multiple resources
  • Drag and drop operations (event moving, resizing, creating) enabled by default
  • 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.

React Scheduler Configurator

react-scheduler-component-configurator.png

Instead of creating the React project from scratch you can use the online Scheduler UI Builder application to configure the React Scheduler component and download a  ready-to-run project.

Installing DayPilot Pro React Module

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 --save https://npm.daypilot.org/daypilot-pro-react/trial/2018.2.3297.tar.gz

Or using yarn:

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

The React module is available since version 2018.2.3297.

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

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 using the default configuration - without any resources or events:

react-scheduler-component-empty.png

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

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

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

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.

Loading Scheduler Event Data

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: "2018-05-01",
            days: 31,
            scale: "Day",
            // ...
        };
    }

    render() {
        var {...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 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: "2018-05-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: "2018-05-02T00:00:00", end: "2018-05-05T00:00:00", resource: "A" },
                {id: 2, text: "Event 2", start: "2018-05-03T00:00:00", end: "2018-05-10T00:00:00", resource: "C", barColor: "#38761d", barBackColor: "#93c47d" },
                {id: 3, text: "Event 3", start: "2018-05-02T00:00:00", end: "2018-05-08T00:00:00", resource: "D", barColor: "#f1c232", barBackColor: "#f1c232" },
                {id: 3, text: "Event 3", start: "2018-05-02T00:00:00", end: "2018-05-08T00:00:00", resource: "E", barColor: "#cc0000", barBackColor: "#ea9999" }
            ]
        };
    }

    render() {
        var {...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.

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) {
        var newLevel = ev.target.value;

        this.setState({
            level: newLevel
        });

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

    }

    render() {
        return (
            <div className="space">
                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>
            </div>
        );
    }
}

export default Zoom;

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

  • Month
  • Week

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: "2018-05-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() {
        var {...config} = this.state;
        return (
            <div>
                <Zoom onChange={args => this.zoomChange(args)} />

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

export default Scheduler;

Listening to Scheduler Drag and Drop Events

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

Invoking Scheduler Methods

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 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)}
    // ...
/>

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: "2018-05-01",
            days: 31,
            scale: "Day",
            timeHeaders: [

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

            ],
            cellWidthSpec: "Auto",
            eventHeight:30,
            cellWidth: 50,
            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: "2018-05-02T00:00:00", end: "2018-05-05T00:00:00", resource: "A" },
                {id: 2, text: "Event 2", start: "2018-05-03T00:00:00", end: "2018-05-10T00:00:00", resource: "C", barColor: "#38761d", barBackColor: "#93c47d" },
                {id: 3, text: "Event 3", start: "2018-05-02T00:00:00", end: "2018-05-08T00:00:00", resource: "D", barColor: "#f1c232", barBackColor: "#f1c232" },
                {id: 3, text: "Event 3", start: "2018-05-02T00:00:00", end: "2018-05-08T00:00:00", resource: "E", barColor: "#cc0000", barBackColor: "#ea9999" }
            ]
        };
    }

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

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

    render() {
        var {...config} = this.state;
        return (
            <div>
                <Zoom onChange={args => this.zoomChange(args)} />
                <div className="space"><label><input type="checkbox" checked={this.state.cellWidthSpec === "Auto"} onChange={ev => this.cellWidthChange(ev)} /> Auto width</label></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);
                    }}
                    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

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