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.
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.
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.
When the dragged reaches the Scheduler it is transformed into the native moving shadow object which highlights the target position and duration:
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.
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
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 the 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() {
const src = document.getElementsByClassName("drag-item");
for (let item of src) {
item.setAttribute("draggable", "true");
item.addEventListener("dragstart", ev => {
const data = {
id: item.dataset.id,
name: item.dataset.name
};
ev.dataTransfer.setData("daypilot/external-item", JSON.stringify(data));
});
item.addEventListener("dragend", ev => {
if (ev.dataTransfer.dropEffect === "move") {
item.parentElement.removeChild(item);
}
});
}
}
The activateItemsForDragDrop()
function finds all elements marked with "drag-item"
CSS class and enables dragging by adding a draggable
attribute. It also attaches custom JSON data using a custom "daypilot/external-item"
drag type.
Converting Items to Scheduler Events
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.
const dp = new DayPilot.Scheduler("dp", {
// ...
onAfterCellRender: args => {
args.div.addEventListener("dragover", 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';
}
args.div.classList.add("dragging-over");
});
args.div.addEventListener("dragleave", ev => {
args.div.classList.remove("dragging-over");
});
args.div.addEventListener("drop", ev => {
ev.preventDefault();
ev.stopPropagation();
const daypilotJson = ev.dataTransfer.getData("daypilot/external-item");
if (daypilotJson) {
const data = JSON.parse(daypilotJson);
const e = {
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
event handlers make sure that the target cell is highlighted when the external item is dragged over it.
The drop
event handler reads the attached item data and creates a new Scheduler event at the target location.
Dragging Files From Desktop to the Scheduler
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:
const dp = new DayPilot.Scheduler("dp", {
// ...
onAfterCellRender: args => {
args.div.addEventListener("dragover", 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';
}
args.div.classList.add("dragging-over");
});
args.div.addEventListener("dragleave", ev => {
args.div.classList.remove("dragging-over");
});
args.div.addEventListener("drop", ev => {
ev.preventDefault();
ev.stopPropagation();
console.log("types", ev.dataTransfer.types);
console.log("files", ev.dataTransfer.files);
const daypilotJson = ev.dataTransfer.getData("daypilot/external-item");
if (daypilotJson) {
const data = JSON.parse(daypilotJson);
const e = {
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) {
const e = {
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:
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 type="text/css">
.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>
const 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: async args => {
const form = [
{ name: "Event name", id: "name"}
];
const data = {
name: "Event 1"
};
const modal = await DayPilot.Modal.form(form, data);
dp.clearSelection();
if (modal.canceled) { return; }
dp.events.add({
start: args.start,
end: args.end,
id: DayPilot.guid(),
resource: args.resource,
text: modal.result.name
});
},
treeEnabled: true,
onAfterCellRender: args => {
args.div.addEventListener("dragover", 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';
}
args.div.classList.add("dragging-over");
});
args.div.addEventListener("dragleave", ev => {
args.div.classList.remove("dragging-over");
});
args.div.addEventListener("drop", ev => {
ev.preventDefault();
ev.stopPropagation();
console.log("types", ev.dataTransfer.types);
console.log("files", ev.dataTransfer.files);
const daypilotJson = ev.dataTransfer.getData("daypilot/external-item");
if (daypilotJson) {
const data = JSON.parse(daypilotJson);
const e = {
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) {
const e = {
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() {
const src = document.getElementsByClassName("drag-item");
for (let item of src) {
item.setAttribute("draggable", "true");
item.addEventListener("dragstart", ev => {
const data = {
id: item.dataset.id,
name: item.dataset.name
};
ev.dataTransfer.setData("daypilot/external-item", JSON.stringify(data));
});
item.addEventListener("dragend", ev => {
if (ev.dataTransfer.dropEffect === "move") {
item.parentElement.removeChild(item);
}
});
}
}
</script>
</body>
</html>