Overview

This tutorial shows how to customize the JavaScript Scheduler drag and drop behavior to swap events:

  • If an event is dropped on an existing event the events will be swapped
  • A hint is displayed to the user during dragging
  • If there are multiple events at the target location, the drop will be forbidden
  • Includes a trial version of DayPilot Pro for JavaScript (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.

Live Demo

Swapping Events on Drop

Moving a Scheduler event:

javascript-scheduler-event-swapping-drag-and-drop-before.png

After drop:

javascript-scheduler-event-swapping-drag-and-drop-after.png

The JavaScript Scheduler component allows customization of the drop action using onEventMove event handler. We will use it to implement event swapping feature.

First, we detect the target events:

onEventMove: function(args) {
  var targetRow = dp.rows.find(args.newResource);
  var existing = targetRow.events.forRange(args.newStart, args.newEnd);
  if (existing.length === 0) {
    return;
  }

  // ...

}

If there are more events at the target position, the operation will not be allowed:

  // ...

  if (existing.length > 1) {
    DayPilot.Modal.alert("More than one event at the target position");
    args.preventDefault();
    return;
  }

  /...

If there is exactly one event (which is not the source event), move it to the source position:

  // ...

  var sourceEvent = args.e;
  var targetEvent = existing[0];

  if (sourceEvent.data.id === targetEvent.data.id) {
    return;
  }

  targetEvent.data.start = sourceEvent.data.start;
  targetEvent.data.end = sourceEvent.data.end;
  targetEvent.data.resource = sourceEvent.data.resource;

  dp.events.update(targetEvent);

  // ...

Full onEventMove implementation:

onEventMove: function(args) {
  var targetRow = dp.rows.find(args.newResource);
  var existing = targetRow.events.forRange(args.newStart, args.newEnd);
  if (existing.length === 0) {
    return;
  }

  if (existing.length > 1) {
    DayPilot.Modal.alert("More than 1 event at the target position");
    args.preventDefault();
    return;
  }

  var sourceEvent = args.e;
  var targetEvent = existing[0];

  if (sourceEvent.data.id === targetEvent.data.id) {
    return;
  }

  targetEvent.data.start = sourceEvent.data.start;
  targetEvent.data.end = sourceEvent.data.end;
  targetEvent.data.resource = sourceEvent.data.resource;

  var delayed = elements.delayed.checked;

  if (delayed) {
    dp.events.remove(targetEvent);
    setTimeout(function() {
      dp.events.add(targetEvent);
    }, 200);
  }
  else {
    dp.events.update(targetEvent);
  }

}

Drag Feedback

javascript-scheduler-event-swapping-drag-and-drop-feedback.png

In order to provide better feedback on what is going to happen on drop, we will add onEventMoving handler to the Scheduler and display a hint to the user:

onEventMoving: function(args) {
  var targetRow = dp.rows.find(args.resource);
  var existing = targetRow.events.forRange(args.start, args.end);
  if (existing.length === 0) {
    return;
  }

  if (existing.length > 1) {
    args.right.enabled = true;
    args.right.html = "More than 1 event at the target position, unable to swap";
    args.allowed = false;
    return;
  }

  var sourceEvent = args.e;
  var targetEvent = existing[0];

  if (sourceEvent.data.id === targetEvent.data.id) {
    return;
  }

  args.right.enabled = true;
  args.right.html = "Events will be swapped";
}

Full Source Code

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <title>JavaScript Scheduler: Swapping Events (Drag and Drop)</title>

  <!-- DayPilot library -->
  <script src="js/daypilot/daypilot-all.min.js"></script>
</head>
<body>
<div class="header">
  <h1><a href='https://code.daypilot.org/42265/javascript-scheduler-swapping-events-drag-and-drop'>JavaScript Scheduler: Swapping Events (Drag and Drop)</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="space">
  <label><input type="checkbox" id="delayed"> Delayed effect</label>
  </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: 31,
    startDate: "2019-07-01",
    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
        }));
      });
    },
    onEventMove: function(args) {
      var targetRow = dp.rows.find(args.newResource);
      var existing = targetRow.events.forRange(args.newStart, args.newEnd);
      if (existing.length === 0) {
        return;
      }


      if (existing.length > 1) {
        DayPilot.Modal.alert("More than 1 event at the target position");
        args.preventDefault();
        return;
      }

      var sourceEvent = args.e;
      var targetEvent = existing[0];

      if (sourceEvent.data.id === targetEvent.data.id) {
        return;
      }

      targetEvent.data.start = sourceEvent.data.start;
      targetEvent.data.end = sourceEvent.data.end;
      targetEvent.data.resource = sourceEvent.data.resource;

      var delayed = elements.delayed.checked;

      if (delayed) {
        dp.events.remove(targetEvent);
        setTimeout(function() {
          dp.events.add(targetEvent);
        }, 200);
      }
      else {
        dp.events.update(targetEvent);
      }

    },
    onEventMoving: function(args) {
      var targetRow = dp.rows.find(args.resource);
      var existing = targetRow.events.forRange(args.start, args.end);
      if (existing.length === 0) {
        return;
      }

      if (existing.length > 1) {
        args.right.enabled = true;
        args.right.html = "More than 1 event at the target position, unable to swap";
        args.allowed = false;
        return;
      }

      var sourceEvent = args.e;
      var targetEvent = existing[0];

      if (sourceEvent.data.id === targetEvent.data.id) {
        return;
      }

      args.right.enabled = true;
      args.right.html = "Events will be swapped";
    },
    treeEnabled: true,
  });
  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"}
  ];
  dp.events.list = [{
    "start": "2019-07-06T00:00:00",
    "end": "2019-07-12T00:00:00",
    "id": "20e96797-c133-fd5b-3a14-0ef027886ded",
    "resource": "R1",
    "text": "Event 1",
    "barColor": "#cc0000"
  }, {
    "start": "2019-07-09T00:00:00",
    "end": "2019-07-15T00:00:00",
    "id": "6ad9fba8-f673-3496-138c-394853c43785",
    "resource": "R2",
    "text": "Event 2"
  }];
  dp.init();

  var elements = {
    delayed: document.getElementById("delayed")
  };
</script>

</body>
</html>