Overview

  • The context menu can be triggered manually - you can add it to any DOM element by binding oncontextmenu event.

  • You can also display the context menu using a hover icon added as an active area.

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

Unscheduled Items as Standard <div> Elements

javascript-scheduler-context-menu-div-elements.png

The JavaScript Scheduler component allows dragging external items to the grid. The external items can be standard HTML elements, such as <li> or <div>. In this example, we will use <div> elements:

HTML

<div class="queue-list">
  <div class="queue-item" data-text="Task 1" data-duration="2" data-id="1"></div>
  <div class="queue-item" data-text="Task 2" data-duration="3" data-id="2"></div>
</div>

Make the Items Draggable

javascript-scheduler-make-element-draggable.png

To become draggable, the <div> elements need to be activated:

var items = Array.apply(null, document.querySelectorAll(".queue-item"));
items.forEach(function(item) {
  makeDraggable(item);
});

You can activate the elements using DayPilot.Scheduler.makeDraggable() method. This method will add the necessary handlers that will allow moving the items to the Scheduler (work on desktop and touch devices).

When activating the items, you need to specify basic properties (like text, duration, and id). These properties will be used to create an event on drop.

function makeDraggable(element) {
  var durationInDays = parseInt(element.dataset.duration, 10);
  console.log(durationInDays);
  var durationInSeconds = durationInDays * 1440 * 60;

  var id = element.dataset.id;
  var text = element.dataset.text;

  DayPilot.Scheduler.makeDraggable({
    element: element,
    text: text,
    duration: durationInSeconds,
    id: id
  });
}

Right Click Context Menu

javascript-scheduler-add-context-menu-to-div-element.png

Usually, the external items are styled similarly to the events displayed in the Scheduler. You might want to add a context menu to the external items to make the UI more consistent.

Let’s define a context menu with “Edit” and “Delete” items. The actual logic is omitted for brevity but you can find it in the downloadable project.

var menu = new DayPilot.Menu({
  items: [
    {
      text: "Edit...",
      onClick: function(args) { /* ... */ }
    },
    {
      text: "Delete",
      onClick: function(args) { /* ... */ }
    },
  ]
});

In order to add the context menu, we need to find all <div> elements of unscheduled tasks. They are marked with .queue-item CSS class:

var items = Array.apply(null, document.querySelectorAll(".queue-item"));
items.forEach(function(item) {
  // ...
  activateContextMenu(item);
});

In the activateContextMenu() method, we add a contextmenu event handler and open the menu using DayPilot.Menu.show() method. The default browser menu has to be canceled by calling ev.preventDefault().

function activateContextMenu(element) {
  element.addEventListener("contextmenu", function(ev) {
    ev.preventDefault();
    menu.show(element);
  });
}

Context Menu Hover Icon

javascript-scheduler-add-context-menu-active-area.png

It is also possible to add a hover icon that will provide users a hint that additional options are available. The hover icon can be added using active areas. The active areas can be added to custom DOM elements using DayPilot.Areas.attach() method:

function attachActiveArea(element) {
  var areas = [
    {
      top: 8,
      right: 8,
      height: 14,
      html: "&ctdot;", // three dots
      visibility: "Hover",
      action: "ContextMenu",
      menu: menu,
      style: "font-size: 12px; background-color: rgba(255, 255, 255, .5); border: 1px solid #aaa; padding: 3px 5px; cursor:pointer; color: #333;"
    }
  ];

  var source = element;
  DayPilot.Areas.attach(element, source, { areas: areas});
}

Full Source Code

Here is the full source code of our example, which activates a list of <div> elements as external items draggable to the Scheduler and adds a context menu that can be displayed using right click and a hover icon:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>JavaScript Scheduler: Context menu for external items</title>

  <!-- ... -->

  <link href="external.css" type="text/css" rel="stylesheet" />

  <!-- DayPilot library -->
  <script src="js/daypilot/daypilot-all.min.js"></script>
</head>
<body>
<div class="header">
  <h1><a href='https://code.daypilot.org/96553/javascript-scheduler-context-menu-for-external-items'>JavaScript Scheduler: Context menu for external items</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="left">
    <div class="queue">
      <div class="queue-title">Unscheduled Tasks</div>
      <div class="queue-list">
        <div class="queue-item" data-text="Task 1" data-duration="2" data-id="1"></div>
        <div class="queue-item" data-text="Task 2" data-duration="3" data-id="2"></div>
      </div>
    </div>
  </div>
  <div class="right">
    <div id="dp"></div>
    <div class="generated">Generated using <a href="https://builder.daypilot.org/">DayPilot UI Builder</a>.</div>
  </div>


</div>

<script>
  var 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: function (args) {
      var dp = this;
      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
        }));
      });
    },
  });
  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"},
  ];
  dp.events.list = [];
  dp.init();
</script>


<script>
  var menu = new DayPilot.Menu({
    items: [
      {
        text: "Edit...",
        onClick: function(args) {
          var form = [
            { name: "Text", id: "text"},
            { name: "Duration", id: "duration", options: [
                {name: "1 day", id: 1},
                {name: "2 days", id: 2},
                {name: "3 days", id: 3},
                {name: "4 days", id: 4},
              ]},
          ];
          DayPilot.Modal.form(form, args.source.dataset).then(function(modal) {
            if (modal.canceled) {
              return;
            }
            var result = modal.result;
            for (var name in result) {
              args.source.dataset[name] = result[name];
            }
          });
        }
      },
      {
        text: "Delete",
        onClick: function(args) {
          var element = args.source;

          // doesn't work in IE
          element.remove();
        }
      },
    ]
  });

  var items = Array.apply(null, document.querySelectorAll(".queue-item"));
  items.forEach(function(item) {
    makeDraggable(item);
    activateContextMenu(item);
    attachActiveArea(item);
  });

  function makeDraggable(element) {
    var durationInDays = parseInt(element.dataset.duration, 10);
    console.log(durationInDays);
    var durationInSeconds = durationInDays * 1440 * 60;

    var id = element.dataset.id;
    var text = element.dataset.text;

    DayPilot.Scheduler.makeDraggable({
      element: element,
      text: text,
      duration: durationInSeconds,
      id: id
    });
  }

  function activateContextMenu(element) {
    element.addEventListener("contextmenu", function(ev) {
      ev.preventDefault();
      menu.show(element);
    });
  }

  function attachActiveArea(element) {
    var areas = [
      {
        top: 8,
        right: 8,
        height: 14,
        html: "&ctdot;", // three dots
        visibility: "Hover",
        action: "ContextMenu",
        menu: menu,
        style: "font-size: 12px; background-color: rgba(255, 255, 255, .5); border: 1px solid #aaa; padding: 3px 5px; cursor:pointer; color: #333;"
      }
    ];

    var source = element;
    DayPilot.Areas.attach(element, source, { areas: areas});
  }
</script>

</body>
</html>