Overview

  • Use Shift+drag to start selecting a time range over an existing event. The sample 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.

Overriding the Scheduler Grid Mousedown Behavior

If you want to override the default action that will be started on mousedown event on the JavaScript Scheduler grid, you can use the onGridMouseDown event. This event is fired for the inner Scheduler grid (cells and events).

By default, a simple mousedown will trigger time range selecting for grid cells. For events, the behavior depends on the location and enabled features - it can be drag and drop event moving, resizing, click action or the action defined by active areas.

Shift+mousedown will start rectangle selection, if it is enabled:

onGridMouseDown: (args) => {
  if (args.shift) {
    args.action = "RectangleSelect";
  }
},

We will use this feature to allow time range selection to be started over events (for Shift+drag).

First, we need to enable rectangle selection:

rectangleSelectHandling: "Enabled",

The rectangle selection lets you select a specified Scheduler area. By default, it selects any rectangular area. We want it to limit the selection to just a single row:

rectangleSelectMode: "Row",

Now, we need to specify the action using the onRectangleSelecting event handler. This event is fired in real time whenever the rectangle selection changes:

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

In our event handler, we hide the rectangle element using args.visible and create a custom selection using the time range selection API.

As soon as the rectangle selection is complete (the mouse button is released) the Scheduler fires the onRectangleSelected event. We will use the event handler to define the action - it will be the same action that you normally define for onTimeRangeSelected:

onRectangleSelected: async (args) => {
  const selection = dp.multirange.get()[0];
  if (!selection) {
    return;
  }
  await app.createEvent(selection);
}

The app.createEvent() method is shared with ordinary time range selection. It waits for the prompt, clears the selection after either confirmation or cancellation, and adds an event only if the user entered a nonempty name. The existing event stays in place; the new event can overlap it.

const app = {
  async createEvent(selection) {
    const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 2");
    dp.clearSelection();
    if (modal.canceled || !modal.result) {
      return;
    }
    dp.events.add({
      start: selection.start,
      end: selection.end,
      id: DayPilot.guid(),
      resource: selection.resource,
      text: modal.result
    });
  },
  // ...
};

Running the Sample

Install the dependencies with npm install, then run npm run dev and open the local URL printed by Vite. The sample displays the current month and includes Event 1 on Resource 2. Hold Shift and drag over that event, then release the mouse button to enter the new event name. Changes are stored in memory and reset when you reload the page.

The project uses the DayPilot UI Builder npm layout: index.html hosts the Scheduler, and src/app.js configures it.

Full Source Code

The complete Scheduler implementation in src/app.js:

import {DayPilot} from "daypilot-pro-javascript";

const dp = new DayPilot.Scheduler("dp", {
  timeHeaders: [{groupBy: "Month"}, {groupBy: "Day", format: "d"}],
  scale: "Day",
  days: DayPilot.Date.today().daysInMonth(),
  startDate: DayPilot.Date.today().firstDayOfMonth(),
  timeRangeSelectedHandling: "Enabled",
  onTimeRangeSelected: async (args) => {
    await app.createEvent(args);
  },
  rectangleSelectHandling: "Enabled",
  rectangleSelectMode: "Row",
  onGridMouseDown: (args) => {
    if (args.shift) {
      args.action = "RectangleSelect";
    }
  },
  onRectangleSelecting: (args) => {
    args.visible = false;
    dp.multirange.clear();
    dp.multirange.add({start: args.start, end: args.end, resource: args.resources[0]});
  },
  onRectangleSelected: async (args) => {
    const selection = dp.multirange.get()[0];
    if (!selection) {
      return;
    }
    await app.createEvent(selection);
  }
});
dp.init();

const app = {
  async createEvent(selection) {
    const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 2");
    dp.clearSelection();
    if (modal.canceled || !modal.result) {
      return;
    }
    dp.events.add({
      start: selection.start,
      end: selection.end,
      id: DayPilot.guid(),
      resource: selection.resource,
      text: modal.result
    });
  },
  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 start = dp.startDate;
    const events = [
      {
        id: 1,
        start: start.addDays(4),
        end: start.addDays(10),
        resource: "R2",
        text: "Event 1"
      }
    ];
    dp.update({resources, events});
  }
};
app.init();