Overview

License

Licensed for testing and evaluation purposes. Please see the license agreement included in the sample package. You can use the source code of the tutorial if you are a licensed user of DayPilot Pro for JavaScript.

JavaScript Scheduler: Rectangle Selection

javascript scheduler time range rows rectangle

In addition to the standard drag and drop operations, the Scheduler supports selecting a custom rectangle area. It has to be enabled using the rectangleSelectHandling property.

Selecting Events using the Rectangle

The built-in rectangle action can select all events that overlap the dragged area. Set rectangleSelectHandling to "EventSelect" when you want the Scheduler to manage the event selection for you.

rectangleSelectHandling: "EventSelect",
rectangleSelectMode: "Free",

Shift + drag activates the rectangle selection and displays the corresponding rectangle:

javascript scheduler time range rows rectangle event selection

The underlying events are selected when you release the mouse button:

javascript scheduler time range rows rectangle event selection complete

Selecting a Time Range

We will use the same rectangle gesture to create a custom time range selection that spans multiple adjacent rows. In this case, set rectangleSelectHandling to "Enabled" so the Scheduler fires the rectangle events without applying the default event-selection action.

rectangleSelectMode: "Free",
rectangleSelectHandling: "Enabled",

Next, add an onRectangleSelecting event handler. This handler runs while the user is dragging. It hides the normal rectangle overlay and replaces it with a time range multi-selection, one range for each resource row under the rectangle.

onRectangleSelecting: (args) => {
  args.visible = false;
  dp.multirange.clear();
  args.resources.forEach((resource) => {
    dp.multirange.add({start: args.start, end: args.end, resource});
  });
},

The call to args.visible = false hides the rectangle itself. The visible feedback comes from the multi-range selection created by dp.multirange.add().

Handling the Multi-Row Selection

javascript scheduler time range rows selection

Normally, you would use the onTimeRangeSelect event to handle time-range selections. In this case, that event is not fired by the rectangle gesture because the selected ranges are created using the API.

Instead, use the onRectangleSelect event handler. It fires when the user releases the mouse button, after the final rectangle coordinates and resource list are known.

onRectangleSelect: () => {
  dp.multirange.get().forEach((range) => {
    if (!range.allowed) {
      return;
    }
    dp.events.add({
      start: range.start,
      end: range.end,
      resource: range.resource,
      text: "Event",
      id: DayPilot.guid()
    });
  });

  dp.multirange.clear();
}

The example creates a new event in every selected row where the range is allowed. With allowEventOverlap set to false, conflicting rows are skipped. Finally, dp.multirange.clear() removes the temporary selection highlight.

Full Source Code

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>JavaScript Scheduler: Select a Time Range over Multiple Rows</title>

  <style type="text/css">
    p, body, td, input, select, button { font-family: -apple-system,system-ui,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif; font-size: 14px; }
    body { padding: 0px; margin: 0px; background-color: #ffffff; }
    a { color: #1155a3; }
    .space { margin: 10px 0px 10px 0px; }
    .header { background: #003267; background: linear-gradient(to right, #011329 0%,#00639e 44%,#011329 100%); padding:20px 10px; color: white; box-shadow: 0px 0px 10px 5px rgba(0,0,0,0.75); }
    .header a { color: white; }
    .header h1 a { text-decoration: none; }
    .header h1 { padding: 0px; margin: 0px; }
    .main { padding: 10px; margin-top: 10px; }
    .generated { color: #999; }
    .generated a { color: #999; }
  </style>

  <!-- DayPilot library -->
  <script src="js/daypilot/daypilot-all.min.js"></script>
</head>
<body>
<div class="header">
  <h1><a href='https://code.daypilot.org/85404/javascript-scheduler-select-a-time-range-over-multiple-rows'>JavaScript Scheduler: Select a Time Range over Multiple Rows</a></h1>
  <div><a href="https://javascript.daypilot.org/">DayPilot for JavaScript</a> - HTML5 Calendar/Scheduling Components for JavaScript/Angular/React/Vue</div>
</div>

<div class="main">
  <div class="space">Use Shift+drag to select a time range for multiple rows.</div>
  <div id="dp"></div>
  <div class="generated">Generated using <a href="https://builder.daypilot.org/">DayPilot UI Builder</a>.</div>
</div>

<script>
  const monthStart = DayPilot.Date.today().firstDayOfMonth();

  const dp = new DayPilot.Scheduler("dp", {
    timeHeaders: [{"groupBy":"Month"},{"groupBy":"Day","format":"d"}],
    scale: "Day",
    days: monthStart.daysInMonth(),
    startDate: monthStart,
    timeRangeSelectedHandling: "Enabled",
    onTimeRangeSelected: async (args) => {
      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
      });
    },
    rectangleSelectMode: "Free",
    rectangleSelectHandling: "Enabled",
    allowEventOverlap: false,
    onRectangleSelecting: (args) => {
      args.visible = false;
      dp.multirange.clear();
      args.resources.forEach((resource) => {
        dp.multirange.add({start: args.start, end: args.end, resource});
      });
    },
    onRectangleSelect: () => {
      dp.multirange.get().forEach((range) => {
        if (!range.allowed) {
          return;
        }
        dp.events.add({
          start: range.start,
          end: range.end,
          resource: range.resource,
          text: "Event",
          id: DayPilot.guid()
        });
      });

      dp.multirange.clear();
    }
  });
  dp.init();

  const app = {
    init() {
      const resources = [
        {name: "Resource 1", id: "R1"},
        {name: "Resource 2", id: "R2"},
        {name: "Resource 3", id: "R3"},
        {name: "Resource 4", id: "R4"},
        {name: "Resource 5", id: "R5"},
        {name: "Resource 6", id: "R6"},
        {name: "Resource 7", id: "R7"},
        {name: "Resource 8", id: "R8"},
        {name: "Resource 9", id: "R9"},
      ];
      const events = [
        {text: "Event 1", start: monthStart.addDays(11), end: monthStart.addDays(16), resource: "R3", id: 1},
        {text: "Event 2", start: monthStart.addDays(15), end: monthStart.addDays(20), resource: "R5", id: 2},
        {text: "Event 3", start: monthStart.addDays(9), end: monthStart.addDays(15), resource: "R7", id: 3},
      ];
      dp.update({resources, events});
    }
  };
  app.init();
</script>

</body>
</html>