Overview

  • Uses DayPilot Pro for JavaScript with keyboard navigation enabled

  • The Scheduler supports keyboard navigation in the grid

  • You can select a cell using the Enter key

  • You can select multiple cells in a row using Shift + left or Shift + right

  • Pressing Enter when an existing event is focused opens an edit dialog

  • Keyboard navigation can be used in combination with inline event editing

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

Run the Sample

Install the dependencies using npm install, then start the development server using npm run dev. Open the local URL printed in the terminal. You can also try the JavaScript Scheduler: Keyboard Navigation demo.

The sample starts with an empty grid and scrolls to today. Use the arrow keys to focus a cell. Inline editing is enabled initially; turn off the Inline editing switch to use modal dialogs. The sample uses component-scoped keyboard navigation: click inside the Scheduler to give it focus after using the toolbar. Events are stored in memory and disappear when you reload the page.

Enable Keyboard Navigation for the JavaScript Scheduler

enable keyboard access

You can enable keyboard navigation in the JavaScript Scheduler component using the keyboardEnabled property:

const dp = new DayPilot.Scheduler("dp", {
  // ...
  keyboardEnabled: true,
  // ...
});

By default, the keyboard event handlers are attached to the document. To bind them to the Scheduler element instead, set keyboardTarget to "component". The sample uses this setting so the toolbar controls can keep their normal keyboard behavior:

const dp = new DayPilot.Scheduler("dp", {
  // ...
  keyboardEnabled: true,
  keyboardTarget: "component",
  // ...
});

Click inside the Scheduler to give it focus. Arrow keys then navigate its grid; while a toolbar switch has focus, Space toggles that switch instead.

When the Scheduler with keyboard access is initialized, no cell is focused and the view is standard:

navigation activated

As soon as you press one of the arrow keys, the cell which is displayed in the upper-left corner of the current Scheduler viewport will be focused:

focused cell

Now you can navigate the Scheduler grid using arrow keys.

Create Events using Keyboard

Press Enter to select the focused cell. The Scheduler fires the onTimeRangeSelect and onTimeRangeSelected event handlers. Use onTimeRangeSelected to create an event. Turn off the Inline editing switch to try the modal dialog shown below:

create event

Our onTimeRangeSelected event handler opens a modal dialog using DayPilot.Modal dialog:

onTimeRangeSelected: async (args) => {
  if (app.elements.inline.checked) {
    // ...
  }
  else {
    const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 1");
    dp.clearSelection();
    app.focusScheduler();
    if (modal.canceled || !modal.result) {
      return;
    }
    dp.events.add({
      start: args.start,
      end: args.end,
      id: DayPilot.guid(),
      resource: args.resource,
      text: modal.result
    });
  }
},

To create an event spanning multiple cells in one row, hold Shift and press the left or right arrow key. Release Shift to complete the selection and open the editor. In modal mode, the selected range remains visible behind the dialog until you confirm or cancel:

create event multiple cells

Edit Events using Keyboard

When there is an existing event at the current location, the Scheduler will automatically focus it:

focus event

Press Enter when an event is focused to fire onEventClick and onEventClicked. In modal mode, the onEventClick handler opens a dialog with the existing text:

edit event

And this is our onEventClick event handler:

onEventClick: async (args) => {
  if (app.elements.inline.checked) {
    // ...
  }
  else {
    const modal = await DayPilot.Modal.prompt("Edit event:", args.e.data.text);
    app.focusScheduler();
    if (modal.canceled) {
      return;
    }
    args.e.data.text = modal.result;
    dp.events.update(args.e);
  }
}

Keyboard Navigation and Inline Editing

It is also possible to use inline event editing in combination with the keyboard navigation. The Scheduler will display an edit box right at the focused location:

inline editing

With the Inline editing switch on, the handlers use dp.events.edit() to open an editor directly over the event. The relevant branches of the handlers are shown below:

onTimeRangeSelected: async (args) => {
  if (app.elements.inline.checked) {
    dp.clearSelection();
    const e = new DayPilot.Event({
      start: args.start,
      end: args.end,
      id: DayPilot.guid(),
      resource: args.resource,
      text: "New event",
      temporary: true
    });
    dp.events.add(e);
    dp.events.edit(e);
  }
  // ...
},
onEventEdited: (args) => {
  if (args.canceled && args.e.data.temporary) {
    dp.events.remove(args.e);
  }
  else {
    args.e.data.temporary = false;
  }
  app.focusScheduler();
},
onEventClick: async (args) => {
  if (app.elements.inline.checked) {
    dp.events.edit(args.e);
  }
  // ...
}

The onTimeRangeSelected handler creates a temporary event and immediately activates editing. Press Enter to save the text or Escape to cancel. The onEventEdited handler removes a canceled temporary event; for an existing event, cancellation preserves its original text. After a successful edit, the handler clears the temporary flag so a later cancellation does not delete the event. The handlers also call app.focusScheduler() to return focus to the Scheduler after editing. This keeps the next arrow or Enter key working with keyboardTarget: "component".

The Wider inline editor (100px) switch controls eventEditMinWidth. Turn it on to set a minimum width of 100 pixels or off to let the editor match the event width.

Full Source Code

The Scheduler configuration and event handlers are in src/app.js. The page in index.html loads this file as a JavaScript module and provides the keyboard hints and editing controls. This example was started using the DayPilot UI Builder.

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().daysInYear(),
  startDate: DayPilot.Date.today().firstDayOfYear(),
  keyboardEnabled: true,
  keyboardTarget: "component",
  eventEditMinWidth: 100,
  heightSpec: "Fixed",
  height: 300,
  onTimeRangeSelected: async (args) => {
    if (app.elements.inline.checked) {
      dp.clearSelection();
      const e = new DayPilot.Event({
        start: args.start,
        end: args.end,
        id: DayPilot.guid(),
        resource: args.resource,
        text: "New event",
        temporary: true
      });
      dp.events.add(e);
      dp.events.edit(e);
    }
    else {
      const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 1");
      dp.clearSelection();
      app.focusScheduler();
      if (modal.canceled || !modal.result) {
        return;
      }
      dp.events.add({
        start: args.start,
        end: args.end,
        id: DayPilot.guid(),
        resource: args.resource,
        text: modal.result
      });
    }
  },
  onEventEdited: (args) => {
    if (args.canceled && args.e.data.temporary) {
      dp.events.remove(args.e);
    }
    else {
      args.e.data.temporary = false;
    }
    app.focusScheduler();
  },
  onEventClick: async (args) => {
    if (app.elements.inline.checked) {
      dp.events.edit(args.e);
    }
    else {
      const modal = await DayPilot.Modal.prompt("Edit event:", args.e.data.text);
      app.focusScheduler();
      if (modal.canceled) {
        return;
      }
      args.e.data.text = modal.result;
      dp.events.update(args.e);
    }
  }
});
dp.init();

const app = {
  elements: {
    inline: document.querySelector("#inline"),
    min: document.querySelector("#min")
  },
  focusScheduler() {
    document.getElementById("dp").focus({preventScroll: true});
  },
  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"},
      {name: "Resource 10", id: "R10"},
      {name: "Resource 11", id: "R11"},
      {name: "Resource 12", id: "R12"},
      {name: "Resource 13", id: "R13"}
    ];
    dp.update({resources, events: []});
    dp.scrollTo(DayPilot.Date.today());
    this.elements.min.addEventListener("change", () => {
      dp.eventEditMinWidth = this.elements.min.checked ? 100 : 0;
    });
  }
};
app.init();