Overview

  • Requires DayPilot Pro for JavaScript 2020.4.4755 or later

  • The Scheduler supports keyboard navigation in the grid

  • You can select a cell using <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. Buy a license.

Live Demo

Enable Keyboard Navigation for the JavaScript Scheduler

javascript-scheduler-enable-keyboard-access.png

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

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

By default, the key event handler are attached to the document element. You can override it using keyboardTarget property. The the other possible value is "component" which binds the events to the main Scheduler element:

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

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

javascript-scheduler-keyboard-navigation-activated.png

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:

javascript-scheduler-keyboard-navigation-focused-cell.png

Now you can navigate the Scheduler grid using arrow keys.

Create Events using Keyboard

If you press <enter> the Scheduler will create a time range selection for the current cell and fire onTimeRangeSelect and onTimeRangeSelected event handler. You can use them to create a new event:

javascript-scheduler-keyboard-navigation-create-event.png

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

onTimeRangeSelected: function (args) {
  DayPilot.Modal.prompt("Create a new event:", "Event 1").then(function(modal) {
    dp.clearSelection();
    if (!modal.result) { return; }
    dp.events.add(new DayPilot.Event({
      start: args.start,
      end: args.end,
      id: DayPilot.guid(),
      resource: args.resource,
      text: modal.result
    }));
  });
},

You can also create a time range spanning multiple cells in a row using Shift + left or Shift + right:

javascript-scheduler-keyboard-navigation-create-event-multiple-cells.png

Edit Events using Keyboard

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

javascript-scheduler-keyboard-navigation-focus-event.png

If you press <enter> the Scheduler will fire onEventClick and onEventClicked events. You can use onEventClick to open a modal dialog that lets you edit the event details:

javascript-scheduler-keyboard-navigation-edit-event.png

And this is our onEventClick event handler:

onEventClick: function(args) {
  DayPilot.Modal.prompt("Edit event:", args.e.data.text).then(function(modal) {
    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:

javascript-scheduler-keyboard-navigation-inline-editing.png

In order to enable inline editing, the event handlers needs to be modified as follows:

onTimeRangeSelected: function (args) {
  dp.clearSelection();
  var 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: function(args) {
  if (args.canceled && args.e.data.temporary) {
    dp.events.remove(args.e);
  }
  else {
    args.e.data.temporary = false;
  }
},
onEventClick: function(args) {
  dp.events.edit(args.e);
},

The onTimeRangeSelected handler now creates a temporary event and immediately activates the editing mode. We have also added onEventEdited event handler to handle the <esc> key during event creation - this way the temporary event can be removed.

Full Source Code

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>JavaScript Scheduler: Keyboard Navigation</title>

  <style type="text/css">
    p, body, td { font-family: Tahoma, Arial, Helvetica, sans-serif; font-size: 10pt; }
    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>

  <style>

    .hints {
      border: 1px solid #ccc;
    }

    .hints td {
      font-size: 14px;
      padding: 5px;
    }

    .key {
      display: inline-block;
      padding: 2px 3px 3px 3px;
      border: 1px solid #ccc;
      border-radius: 2px;
      min-width: 20px;
      text-align: center;
      background-color: #f0f0f0;
    }


    .toolbar {
      margin: 10px 0px;
      font-size: 14px;
      display: flex;
      align-items: center;
    }

    .toolbar select {
      font-size: 14px;
      padding: 3px;
    }

    .toolbar-item {
      display: flex;
      align-items: center;
      margin-left: 5px;
    }

    .toolbar-separator {
      width: 1px;
      height: 28px;
      display: inline-block;
      box-sizing: border-box;
      background-color: #ccc;
      margin-bottom: -8px;
      margin-left: 15px;
      margin-right: 15px;
    }
  </style>

  <!-- DayPilot library -->
  <script src="js/daypilot/daypilot-all.min.js"></script>
</head>
<body>
<div class="header">
  <h1><a href='https://code.daypilot.org/29708/javascript-scheduler-keyboard-navigation'>JavaScript Scheduler: Keyboard Navigation</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">
  <table class="hints">
    <tr>
      <td>Move:</td>
      <td><span class="key">&larr;</span> <span class="key">&rarr;</span> <span class="key">&darr;</span> <span class="key">&uarr;</span></td>
    </tr>
    <tr>
      <td>Create an event:</td>
      <td><span class="key">↵ Enter</span></td>
    </tr>
    <tr>
      <td>Select a time range:</td>
      <td><span class="key">⇧ Shift</span>+<span class="key">&rarr;</span> and <span class="key">⇧ Shift</span>+<span class="key">&larr;</span></td>
    </tr>
    <tr>
      <td>Edit an event:</td>
      <td><span class="key">↵ Enter</span></td>
    </tr>
  </table>
  <div class="toolbar">
    Behavior:
    <div class="toolbar-item">
    <input type="checkbox" id="inline" checked> <label for="inline">Inline editing</label>
    </div>
    <div class="toolbar-separator"></div>
    <div class="toolbar-item">
      Inline edit input min width: &nbsp;
      <select id="min">
        <option value="0">matches cell</option>
        <option value="100" selected>100px</option>
      </select>
    </div>
  </div>
  <div id="dp"></div>
  <div class="generated">Generated using <a href="https://builder.daypilot.org/">DayPilot UI Builder</a>.</div>
</div>

<script>
  var dp = new DayPilot.Scheduler("dp", {
    timeHeaders: [{"groupBy":"Month"},{"groupBy":"Day","format":"d"}],
    scale: "Day",
    days: DayPilot.Date.today().daysInYear(),
    startDate: DayPilot.Date.today().firstDayOfYear(),
    timeRangeSelectedHandling: "Enabled",
    eventEditMinWidth: 100,
    onTimeRangeSelected: function (args) {
      var dp = this;

      if (elements.inline.checked) {
        dp.clearSelection();
        var 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 {
        DayPilot.Modal.prompt("Create a new event:", "Event 1").then(function(modal) {
          dp.clearSelection();
          if (!modal.result) { return; }
          dp.events.add(new DayPilot.Event({
            start: args.start,
            end: args.end,
            id: DayPilot.guid(),
            resource: args.resource,
            text: modal.result
          }));
        });
      }

    },
    onEventEdited: function(args) {
      if (args.canceled && args.e.data.temporary) {
        dp.events.remove(args.e);
      }
      else {
        args.e.data.temporary = false;
      }
    },
    onEventClick: function(args) {
      if (elements.inline.checked) {
        dp.events.edit(args.e);
      }
      else {
        DayPilot.Modal.prompt("Edit event:", args.e.data.text).then(function(modal) {
          if (modal.canceled) {
            return;
          }
          args.e.data.text = modal.result;
          dp.events.update(args.e);
        });
      }
    },
    treeEnabled: true,
    keyboardEnabled: true,
    height: 300
  });
  dp.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.events.list = [];
  dp.scrollTo(DayPilot.Date.today());
  dp.init();

  var elements = {
    inline: document.querySelector("#inline"),
    min: document.querySelector("#min")
  };

  elements.min.addEventListener("change", function(ev) {
    dp.eventEditMinWidth = parseInt(elements.min.value);
  });

</script>

</body>
</html>