Overview

  • How to export the current React Scheduler view to a multi-page PDF document.

  • The export toolbar lets you specify the page orientation (landscape/portrait) and the number of rows per page.

  • You can add custom text to the PDF document (header, page numbers).

  • The exported PDF file with the specified file name will be downloaded immediately.

  • Exporting the Scheduler to PDF is useful when you need to print the scheduler. It lets you fully control the content of each printed page.

  • The React project 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.

Dependencies

Install DayPilot Pro (see npm.daypilot.org for an up-to-date NPM link). DayPilot will be used to display the Scheduler.

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

Install jsPDF. jsPDF is a library that allows PDF generation in JavaScript.

npm install jspdf

React Scheduler Component Configuration

React Scheduler Component Configuration

For an introduction to configuring the React Scheduler component, see the following tutorial:

Our PDF export project uses the following configuration:

const [config, setConfig] = useState({
  timeHeaders: [
    {groupBy:"Month"},
    {groupBy:"Day", format: "d"}
  ],
  scale: "Day",
  days: 30,
  startDate: "2024-05-01",
  onTimeRangeSelected: async (args) => {
    const dp = args.control;
    const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 1");
    dp.clearSelection();
    if (modal.canceled) { return; }
    dp.events.add({
      start: args.start,
      end: args.end,
      id: DayPilot.guid(),
      resource: args.resource,
      text: modal.result
    });
  },
  treeEnabled: true,
});

JSX:

return (
  <div>
    <DayPilotScheduler
      {...config}
      ref={schedulerRef}
      onBeforeEventRender={onBeforeEventRender}
    />
  </div>
);

PDF Export Toolbar

PDF Export Toolbar

Above the Scheduler, there is a toolbar that lets you customize the output PDF appearance.

Page orientation. This option allows users to select the orientation of the pages in the exported PDF. It is implemented as a dropdown menu with two options:

  • "Portrait" for a vertical layout.

  • "Landscape" for a horizontal layout.

Row per page. This dropdown list lets users select the number of rows that will be included in every PDF page. The available options in this dropdown are 10, 20, and 30 rows per page.

return (
  <div>
    <div className={"toolbar"}>
      <div className={"toolbar-item"}><b>Export</b></div>
      Orientation:
      <select onChange={(ev) => setOrientation(ev.target.value)} >
        <option value={"portrait"}>Portrait</option>
        <option value={"landscape"}>Landscape</option>
      </select>
      Rows per page:
      <select onChange={(ev) => setRowsPerPage(parseInt(ev.target.value))} >
        <option>10</option>
        <option>20</option>
        <option>30</option>
      </select>
      <button onClick={() => exportToPdf()}>Download PDF</button>
    </div>
    <DayPilotScheduler
      {...config}
      ref={schedulerRef}
      onBeforeEventRender={onBeforeEventRender}
    />
  </div>
);

Scheduler PDF Export Function

The exportToPdf() function uses the exportAs() method of the React Scheduler to export a section of the Scheduler grid to a JPEG image. This image is then inserted into the PDF file.

  • Each page will contain the specified number of rows (stored in the rowPerPage state variable).

  • The React Scheduler component can export the current viewport, full grid or a selected range. The range export will be used to specify the rows that are to be included in the exported image.

  • The output image is scaled down, if necessary, to fit into a PDF page.

  • The first page includes a header with the text "Scheduler". You can use the doc.text() method to insert additional information to each page, such as a page number.

const exportToPdf = async () => {

  const doc = new jsPDF(orientation, "in", "letter")
  doc.setFontSize(40);
  doc.text(0.5, 1, "Scheduler");

  const rows = getScheduler().rows.all();
  var pages = Math.ceil(rows.length / rowsPerPage);

  for (let i = 0; i < pages; i++) {
    const startId = i*rowsPerPage;
    let endId = i*rowsPerPage + rowsPerPage - 1;
    endId = Math.min(endId, rows.length - 1);

    const image = getScheduler().exportAs("jpeg", {
      area: "range",
      scale: 2,
      resourceFrom: rows[startId].id,
      resourceTo: rows[endId].id,
      quality: 0.95
    });

    const dimensions = image.dimensions();  // pixels
    const maxDimensions = orientation === "landscape" ? { width: 10, height: 7.5} : { width: 7.5, height: 10}; // inches
    const adjustedDimensions = shrink(dimensions, maxDimensions);  // inches

    doc.addImage(image.toDataUri(), 'JPEG', 0.5, 1.5, adjustedDimensions.width, adjustedDimensions.height);

    const last = (i === pages - 1);
    if (!last) {
      doc.addPage();
    }
  }

  const blob = doc.output("blob");
  DayPilot.Util.downloadBlob(blob, "scheduler.pdf");

};

Full Source Code

The is the full source code of our React component that displays an interactive Scheduler and an export toolbar that allows users to export the Scheduler content to a multi-page PDF file.

import React, { useEffect, useRef, useState } from 'react';
import { DayPilot, DayPilotScheduler } from "daypilot-pro-react";
import jsPDF from "jspdf";

const Scheduler = () => {
  const schedulerRef = useRef();

  const [orientation, setOrientation] = useState("portrait");
  const [rowsPerPage, setRowsPerPage] = useState(10);

  const [config, setConfig] = useState({
    timeHeaders: [
      {groupBy:"Month"},
      {groupBy:"Day", format: "d"}
    ],
    scale: "Day",
    days: 30,
    startDate: "2024-05-01",
    onTimeRangeSelected: async (args) => {
      const dp = args.control;
      const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 1");
      dp.clearSelection();
      if (modal.canceled) { return; }
      dp.events.add({
        start: args.start,
        end: args.end,
        id: DayPilot.guid(),
        resource: args.resource,
        text: modal.result
      });
    },
    treeEnabled: true,
  });

  const onBeforeEventRender = (args) => {
    args.data.barHidden = true;
    args.data.backColor = "#6aa84f";
    args.data.borderColor = "#38761d";
    args.data.fontColor = "#ffffff";
  };

  useEffect(() => {

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

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

    // load resource and event data using config
    setConfig(prevConfig => ({
      ...prevConfig,
      events,
      resources
    }));

    // to improve performance, you can use the direct API to load resources and events instead:
    // getScheduler().update({events, resources});

  }, []);

  const getScheduler = () => schedulerRef.current?.control;

  const exportToPdf = async () => {

    const doc = new jsPDF(orientation, "in", "letter")
    doc.setFontSize(40);
    doc.text(0.5, 1, "Scheduler");

    const rows = getScheduler().rows.all();
    var pages = Math.ceil(rows.length / rowsPerPage);

    for (let i = 0; i < pages; i++) {
      const startId = i*rowsPerPage;
      let endId = i*rowsPerPage + rowsPerPage - 1;
      endId = Math.min(endId, rows.length - 1);

      const image = getScheduler().exportAs("jpeg", {
        area: "range",
        scale: 2,
        resourceFrom: rows[startId].id,
        resourceTo: rows[endId].id,
        quality: 0.95
      });

      const dimensions = image.dimensions();  // pixels
      const maxDimensions = orientation === "landscape" ? { width: 10, height: 7.5} : { width: 7.5, height: 10}; // inches
      const adjustedDimensions = shrink(dimensions, maxDimensions);  // inches

      doc.addImage(image.toDataUri(), 'JPEG', 0.5, 1.5, adjustedDimensions.width, adjustedDimensions.height);

      const last = (i === pages - 1);
      if (!last) {
        doc.addPage();
      }
    }

    const blob = doc.output("blob");
    DayPilot.Util.downloadBlob(blob, "scheduler.pdf");

  };

  function shrink(dimensions, max) {
    const widthRatio = dimensions.width / max.width;
    const heightRatio = dimensions.height / max.height;

    let ratio = Math.max(widthRatio, heightRatio);
    ratio = Math.max(ratio, 1);

    const width = dimensions.width / ratio;
    const height = dimensions.height / ratio;
    return { width, height};
  }

  return (
    <div>
      <div className={"toolbar"}>
        <div className={"toolbar-item"}><b>Export</b></div>
        Orientation:
        <select onChange={(ev) => setOrientation(ev.target.value)} >
          <option value={"portrait"}>Portrait</option>
          <option value={"landscape"}>Landscape</option>
        </select>
        Rows per page:
        <select onChange={(ev) => setRowsPerPage(parseInt(ev.target.value))} >
          <option>10</option>
          <option>20</option>
          <option>30</option>
        </select>
        <button onClick={() => exportToPdf()}>Download PDF</button>
      </div>
      <DayPilotScheduler
        {...config}
        ref={schedulerRef}
        onBeforeEventRender={onBeforeEventRender}
      />
    </div>
  );
}
export default Scheduler;