Overview

  • This tutorial shows how to enable drag and drop to JavaScript Scheduler grid cells using HTML5 drag and drop API.
  • This allows dragging custom items from the same page (activated using "draggable" attribute) or from external sources (e.g. files from desktop)
  • The Scheduler grid cells are registered as drop targets. You can convert the dragged items to events or other objects on drop.
  • Requires DayPilot Pro for JavaScript 2019.2.3886 or later.
  • A trial version of DayPilot Pro for JavaScript is included (see also 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.

External Drag and Drop

The JavaScript Scheduler allows dragging custom external items to the Scheduler. It provides DayPilot.Scheduler.makeDraggable() method that allows marking any DOM element as a draggable.

javascript-scheduler-external-drag-drop-source.png

When the dragged reaches the Scheduler it is transformed into the native moving shadow object which highlights the target position and duration:

javascript-scheduler-external-drag-drop-target.png

However, this only works for items activated using the Scheduler API and it doesn't allow dragging external items (such as files) from the desktop or another application.

This tutorial shows how to implement support for the native HTML drag and drop API. You can register the Scheduler grid cells as drop targets and accept items using the native drag and drop API. However, but it's important to remember that it only supports a single grid cell as a target and it can't give any hint about the duration of the event/task. That's a limitation of the HTML drag an drop API which doesn't allow reading the custom data until the item is dropped.

javascript-scheduler-html5-drag-drop.png

Dragging External Items to Existing Events

There is also a related tutorial available that shows how to drag external items to existing events:

JavaScript Scheduler: Events as Drag and Drop Target.

Draggable Items using HTML5 Drag and Drop API

javascript-scheduler-drag-tasks-using-html-drag-drop-api.png

The HTML5 drag and drop API lets you activate custom elements for drag and drop. We will create a few <div> elements that will represent tasks that need to be scheduled:

<div class="drag-item" data-id="1" data-name="Task 1">Task 1</div>
<div class="drag-item" data-id="2" data-name="Task 2">Task 2</div>
<div class="drag-item" data-id="3" data-name="Task 3">Task 3</div>

These items need to be activated for drag and drop:

function activateItemsForDragDrop() {

  var src = document.getElementsByClassName("drag-item");

  for (let item of src) {
    
    item.setAttribute("draggable", "true");

    item.addEventListener("dragstart", function(ev) {
      var data = {
        id: item.dataset.id,
        name: item.dataset.name
      };
      ev.dataTransfer.setData("daypilot/external-item", JSON.stringify(data));
    });

  }

}

The activateItemsForDragDrop() functions finds all elements marked with "drag-item" class and enables dragging by adding "draggable" attribute. It also attaches custom JSON data using a custom "daypilot/external-item" drag type.

Converting Items to Scheduler Events

javascript-scheduler-dragging-over-grid-cells.png

The next step is to activate Scheduler cells to accept the dragged items. We will use onAfterCellRender event to add the drag-related event handlers to the cell div.

  var dp = new DayPilot.Scheduler("dp", {
  
    // ...
  
    onAfterCellRender: function(args) {

      args.div.addEventListener("dragover", function(ev) {
        ev.dataTransfer.dropEffect = 'copy';
        DayPilot.Util.addClass(args.div, "dragging-over");
      });

      args.div.addEventListener("dragleave", function(ev) {
        DayPilot.Util.removeClass(args.div, "dragging-over");
      });

      args.div.addEventListener("drop", function(ev) {
        ev.preventDefault();
        ev.stopPropagation();

        var daypilotJson = ev.dataTransfer.getData("daypilot/external-item");

        if (daypilotJson) {
          var data = JSON.parse(daypilotJson);
          var e = new DayPilot.Event({
            start: args.cell.start,
            end: args.cell.end,
            resource: args.cell.resource,
            id: data.id,
            text: data.name
          });
          dp.events.add(e);
        }

      });
    }
  });

The "dragover" and "dragleave" handlers make sure that the target cell is highlighted when the external item is dragged over it.

The "drop" handler reads the attached item data and creates a new Scheduler event at the target location.

Dragging Files From Desktop to the Scheduler

javascript-scheduler-dragging-files-from-desktop.png

The same mechanism can be used to accept files dragged from the desktop. We just need to modify the onAfterCellRender event handler to read the file information:

var dp = new DayPilot.Scheduler("dp", {

  // ...

  onAfterCellRender: function(args) {

    args.div.addEventListener("dragover", function(ev) {
      ev.stopPropagation();
      ev.preventDefault();
      ev.dataTransfer.dropEffect = 'copy';
      DayPilot.Util.addClass(args.div, "dragging-over");
    });

    args.div.addEventListener("dragleave", function(ev) {
      DayPilot.Util.removeClass(args.div, "dragging-over");
    });

    args.div.addEventListener("drop", function(ev) {
      ev.preventDefault();
      ev.stopPropagation();

      if (ev.dataTransfer.files.length > 0) {
        var e = new DayPilot.Event({
          start: args.cell.start,
          end: args.cell.end,
          resource: args.cell.resource,
          id: DayPilot.guid(),
          text: ev.dataTransfer.files[0].name,
          bubbleHtml: ev.dataTransfer.files[0].name
        });
        dp.events.add(e);
      }

    });
  }
});

The "drop" event handler creates a new Scheduler event and uses the file name as the event text:

javascript-scheduler-dragging-files-from-desktop-drop.png

Full Source Code

The source code is included in the download zip file (see the top of the article).

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <title>JavaScript Scheduler: Dragging Items from Desktop</title>

  <style>
    .drag-container {
      display: flex;
      flex-direction: row;
      margin: 10px 0px;
    }
    .drag-item {
      width: 100px;
      height: 100px;
      display: flex;
      justify-content: center;
      align-items: center;
      border: 1px solid #ccc;
      margin-right: 5px;
    }

    #dp .scheduler_default_cell {
      border-right: 1px solid #eee;
      border-bottom: 1px solid #eee;
      box-sizing: border-box;
    }

    #dp .scheduler_default_cell.dragging-over {
      border: 2px solid red;
    }

    #dp .scheduler_default_matrix_vertical_line,
    #dp .scheduler_default_matrix_horizontal_line {
      display: none;
    }
  </style>

  <!-- DayPilot library -->
  <script src="js/daypilot/daypilot-all.min.js"></script>
</head>
<body>
<div class="header">
  <h1><a href='https://code.daypilot.org/63946/javascript-scheduler-dragging-items-from-desktop'>JavaScript Scheduler: Dragging Items from Desktop</a></h1>
  <div><a href="https://javascript.daypilot.org/">DayPilot for JavaScript</a> - HTML5 Calendar/Scheduling Components for JavaScript/Angular/React</div>
</div>

<div class="main">

  <div class="drag-container">
    <div class="drag-item" data-id="1" data-name="Task 1">Task 1</div>
    <div class="drag-item" data-id="2" data-name="Task 2">Task 2</div>
    <div class="drag-item" data-id="3" data-name="Task 3">Task 3</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().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
        }));
      });
    },
    treeEnabled: true,
    onAfterCellRender: function(args) {

      args.div.addEventListener("dragover", function(ev) {
        var hasMyType = ev.dataTransfer.types.some(function(type) { return type === "daypilot/external-item"; });
        if (hasMyType) {
          ev.preventDefault();
          ev.dataTransfer.dropEffect = "move";
        }
        else {
          ev.stopPropagation();
          ev.preventDefault();
          ev.dataTransfer.dropEffect = 'copy';
        }
        DayPilot.Util.addClass(args.div, "dragging-over");
      });

      args.div.addEventListener("dragleave", function(ev) {
        DayPilot.Util.removeClass(args.div, "dragging-over");
      });

      args.div.addEventListener("drop", function(ev) {
        ev.preventDefault();
        ev.stopPropagation();

        console.log("types", ev.dataTransfer.types);
        console.log("files", ev.dataTransfer.files);

        var daypilotJson = ev.dataTransfer.getData("daypilot/external-item");

        if (daypilotJson) {
          var data = JSON.parse(daypilotJson);
          var e = new DayPilot.Event({
            start: args.cell.start,
            end: args.cell.end,
            resource: args.cell.resource,
            id: data.id,
            text: data.name
          });
          dp.events.add(e);
        }
        else if (ev.dataTransfer.files.length > 0) {
          var e = new DayPilot.Event({
            start: args.cell.start,
            end: args.cell.end,
            resource: args.cell.resource,
            id: DayPilot.guid(),
            text: ev.dataTransfer.files[0].name,
            bubbleHtml: ev.dataTransfer.files[0].name
          });
          dp.events.add(e);
        }

      });
    }
  });
  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();


  activateItemsForDragDrop();

  function activateItemsForDragDrop() {
    var src = document.getElementsByClassName("drag-item");

    for (let item of src) {

      item.setAttribute("draggable", "true");

      item.addEventListener("dragstart", function(ev) {
        var data = {
          id: item.dataset.id,
          name: item.dataset.name
        };
        ev.dataTransfer.setData("daypilot/external-item", JSON.stringify(data));
      });

      item.addEventListener("dragend", function(ev) {
        if (ev.dataTransfer.dropEffect === "move") {
          item.parentElement.removeChild(item);
        }
      });

    }

  }
</script>

</body>
</html>