Dragging unplanned tasks from an external grid
Intro
A popular way of scheduling unplanned tasks is to list them in an external data grid and use drag drop to schedule & assign them. With the Bryntum Scheduler this is very easy to achieve and in this guide we will show you how the dragfromgrid example was built. Please note, the Grid component is licensed separately from the Scheduler component.
Creating the Scheduler and Grid components
This demo has been structured to use a few custom subclasses of Scheduler and Grid to keep things tidy. You can inspect the sources of these classes in the ./lib/ folder. We append our two main components directly to the DOM inside a DIV with id main. The scheduler does not need any special configuration to receive items dragged from the external grid. It is simply configured with a startDate, endDate and crudManager to handle data loading.
let schedule = new Schedule({
ref : 'schedule',
insertFirst: 'main',
startDate : new Date(2025, 11, 1, 8),
endDate : new Date(2025, 11, 1, 18),
flex : 1,
crudManager: {
autoLoad: true,
eventStore : {
storeClass: TaskStore
},
resourceStore : {
modelClass: CustomResourceModel
},
transport : {
load: {
url: 'data/data.json'
}
}
}
});
new Splitter({
appendTo: 'main'
});
const unplannedGrid = new UnplannedGrid({
ref : 'unplanned',
appendTo : 'main',
title : 'Unplanned Tasks',
width : 300,
store : {
modelClass: Task,
readUrl : 'data/unplanned.json',
autoLoad : true
}
});
The data grid uses a plain flat store, which is configured to use the same Task modelClass as the Scheduler (see ./lib/Task.js). The Task model is based on the EventModel.
The containing element lays out its children using basic flex box CSS.
#main {
display : flex;
flex-direction : row;
flex : 1 1 100%;
}
The Splitter is rendered between the components and lets us resize them easily.
Configuring the DragHelper
With the two main components added to the page, it is time to start thinking about the dragging. The DragHelper will provide the drag functionality to allow rows to be dragged from the unplanned grid and dropped as a task on the Scheduler element.
In the demo, the Drag functionality is encapsulated in a custom demo Drag subclass of DragHelper. First, a simple skeleton with some basic configuration is added.
import DragHelper from '../../../lib/Core/helper/DragHelper.js';
export default class Drag extends DragHelper {
static configurable = {
callOnFunctions : true,
// Don't drag the actual row element, clone it
cloneTarget : true,
// We size the cloned element manually
autoSizeClonedTarget : false,
// Only allow drops on the schedule area
dropTargetSelector : '.b-timeline-sub-grid',
// Only allow drag of row elements inside on the unplanned grid
targetSelector : '.b-grid-row:not(.b-group-row)'
};
// Drag start callback
onDragStart({ context }) {
// TODO
}
// Drag callback called every mouse move
onDrag({ event, context }) {
// TODO
}
// Drop callback after a mouse up, take action and transfer the unplanned task to the real EventStore (if it's valid)
onDrop({ context }) {
// TODO
}
};
Above, the following configurations are used:
cloneTargetWe set this totrueto clone the mouse-down element, and not move the actual grid row.targetSelectorThis is a CSS selector defining what elements are draggable, we set it to.b-grid-row:not(.b-group-row)dropTargetSelectorThis is a CSS selector defining where drops are allowed, and we set this to.b-timeline-subgridwhich the Scheduler's timeline sub gridcallOnFunctionsBy setting this totrue, we will receive onXXX ( e.g. onDragStart / onDrop) callbacks which is an easier way of listening for events
Processing drag start
A drag is deemed started when the pointer has moved more than the configured dragThreshold (defaults to 5px). At this time the dragStart event is fired and if using callOnFunctions, the onDragStart callback is called. The first thing to decide is, what should the user visually see when dragging.
Creating the drag proxy element
We want the visual representation of the dragged element to look like an event bar. Hence we define a createProxy method which lets us define the exact HTML markup to use for the drag proxy.
createProxy(element) {
const
proxy = document.createElement('div'),
{ context, schedule } = this,
task = this.grid.getRecordFromElement(element),
durationInPx = schedule.timeAxisViewModel.getDistanceForDuration(task.durationMS);
// Fake an element bar
proxy.classList.add('b-sch-event-wrap', 'b-sch-event', 'b-unassigned-class', `b-sch-${schedule.mode}`);
proxy.innerHTML = `<div class="b-sch-event b-has-content b-sch-event-with-icon">
<div class="b-sch-event-content">
<i class="${task.iconCls}"></i> ${task.name}
</div>
</div>`;
proxy.style.height = `${schedule.rowHeight - (2 * schedule.resourceMargin)}px`;
proxy.style.width = `${durationInPx}px`;
return proxy;
}
In our demo, at this stage we do not want to do too much - only adapt the Grid and Scheduler a bit. For the grid, we just ensure any ongoing cell edit is finalized. And for the Scheduler, we enable scrolling when moving mouse close to edges, to ensure user can reach all parts of the tree. We also disable the EventTooltip feature so it does not show tooltips while we drag over the timeline.
We assign a task reference to the dragged record so we can access it later during the drag process.
onDragStart({ context }) {
const
me = this,
{ schedule } = me,
{ eventTooltip, eventDrag } = schedule.features;
// save a reference to the task so we can access it later
context.task = me.grid.getRecordFromElement(context.grabbed);
// Prevent tooltips from showing while dragging
eventTooltip.disabled = true;
schedule.enableScrollingCloseToEdges(schedule.timeAxisSubGrid);
}
Indicating drag validity
In our demo, the main objective in the onDrag callback is to validate the drop and let the user know if the drop is valid. A special context object is provided to every event / callback, which has a valid boolean indicating drop validity. We look up the current startDate and endDate based on the position of the drag proxy and query the scheduler to know if the resource is available for the specified time slot.
In the code below, we also cache the currently hovered resource record to be able to use it in the onDrop callback.
onDrag({ event, context })
{
const
me = this,
{ schedule } = me,
{ task } = context,
coordinate = DomHelper[`getTranslate${schedule.isHorizontal ? 'X' : 'Y'}`](context.element),
startDate = schedule.getDateFromCoordinate(coordinate, 'round', false),
endDate = startDate && DateHelper.add(startDate, task.duration, task.durationUnit),
// Coordinates required when used in vertical mode, since it does not use actual columns
resource = context.target && schedule.resolveResourceRecord(context.target, [event.offsetX, event.offsetY]);
// Don't allow drops anywhere, only allow drops if the drop is on the timeaxis and on top of a Resource
context.valid &= Boolean(startDate && resource) &&
(schedule.allowOverlap || schedule.isDateRangeAvailable(startDate, endDate, null, resource));
// Save reference to resource so we can use it in onDrop
context.resource = resource;
}
Processing the drop
When a valid drop happens, we remove the dragged record from the unplanned grid store then add it to the Scheduler“s task store and assign it to the correct resource. And lastly, we just to revert the changes made in onDragStart.
onDrop({ context, event }) {
const
me = this,
{ schedule } = me,
{ task, target, resource, valid, element } = context;
// If drop was done in a valid location, set the startDate and transfer the task to the Scheduler event store
if (valid && target) {
const
coordinate = DomHelper[`getTranslate${schedule.isHorizontal ? 'X' : 'Y'}`](element),
date = schedule.getDateFromCoordinate(coordinate, 'round', false);
if (date) {
// Remove from grid first so that the data change
// below does not fire events into the grid.
me.grid.store.remove(task);
task.startDate = date;
task.assign(resource);
schedule.eventStore.add(task);
}
}
schedule.disableScrollingCloseToEdges(schedule.timeAxisSubGrid);
schedule.features.eventTooltip.disabled = false;
}
The final result
Here is a small video showing the final result
Learn moreā¦
Want to learn more about what can be done relating to drag drop with the Bryntum SDKs? Please see these resources: