DragHelper
Intro
A drag drop helper class which lets you move elements in page. It supports:
- Dragging the actual element
- Dragging a cloned version of the element
- Dragging extra
relatedElementsalong with the main element - Firing useful events beforeDragStart, dragStart, drag, drop, abort
- Validation by setting a
validBoolean on the drag context object provided to event listeners - Aborting drag with ESCAPE key
- Constraining drag to be only horizontal or vertical using lockX and lockY
- Defining X / Y boundaries using minX, maxX and minY, maxY
- Async finalization (e.g. to show confirmation prompts)
- Animated final transition after mouse up of a valid drop (see animateProxyTo)
- Animated abort transition after an invalid or aborted drop
//<code-header>
fiddle.title = 'Drag helper';
//</code-header>
const buttonContainer = DomHelper.createElement({
id : 'buttonContainer',
parent : targetElement,
style : {
display : 'flex',
gap : '1em'
}
});
const toolbar = new Toolbar({
insertFirst : targetElement,
style : 'margin-bottom: 2em;width:40em',
items : {
button : {
type : 'button',
disabled : true,
text : 'Drag buttons here'
}
}
});
new Button({
appendTo : buttonContainer,
rendition : 'filled',
text : 'Drag me',
icon : 'fa-tree',
onClick : ({ source: button }) => Toast.show('Merry Xmas')
});
new Button({
appendTo : buttonContainer,
rendition : 'filled',
text : 'Drag me to toolbar',
icon : 'fa-globe',
onClick : ({ source: button }) => Toast.show('Hello World')
});
const dragHelper = new DragHelper({
outerElement : targetElement,
// Only allow drag of buttons inside the fiddle
targetSelector : '#buttonContainer .b-button',
// Only allow drops on the toolbar
dropTargetSelector : '.b-toolbar',
callOnFunctions : true,
onDragStart() {
// Highlight the drop target area
toolbar.element.style.outline = '1px dashed #aaa';
},
async onDrop({ context, event }) {
// Clear the highlight
toolbar.element.style.outline = '';
if (context.valid) {
const button = Widget.fromElement(context.grabbed);
if (toolbar.widgetMap.helpLabel) {
toolbar.remove(toolbar.widgetMap.helpLabel);
}
await this.animateProxyTo(toolbar.contentElement.lastElementChild || Rectangle.content(toolbar.contentElement), {
align : toolbar.contentElement.lastElementChild ? 'l-r' : 'l-l',
// Some offset to account for the margin between buttons
offset : toolbar.contentElement.lastElementChild ? [10, 0] : null
});
toolbar.add(button);
Toast.show('👍 Nice drop!');
}
}
});
fiddle.on('destroy', () => dragHelper.destroy());Two modes
DragHelper supports two modes:
container- moving / rearranging elements within and between specified containerstranslateXY- freely repositioning an element, either using the element or a cloned version of it - a "drag proxy" (default mode)
Container drag mode
Container drag should be used when moving or rearranging child elements within and between specified containers
Example:
// dragging element between containers
let dragHelper = new DragHelper({
mode : 'container',
containers : [ container1, container2 ]
});
Translate drag mode
Use translate drag to reposition an element within its container using translate CSS.
Example:
// dragging element within container
let dragHelper = new DragHelper({
mode : 'translateXY',
targetSelector : 'div.movable'
});
Observable events
In the various events fired by the DragHelper, you will have access to the raw DOM event and some useful context about the drag operation:
myDrag.on({
drag : ({event , context}) {
// The element which we're moving, could be a cloned version of grabbed, or the grabbed element itself
const element = context.element;
// The original mousedown element upon which triggered the drag operation
const grabbed = context.grabbed;
// The target under the current mouse / pointer / touch position
const target = context.target;
}
});
Simple drag helper subclass with a drop target specified:
export default class MyDrag extends DragHelper {
static configurable = {
// Don't drag the actual cell element, clone it
cloneTarget : true,
mode : 'translateXY',
// Only allow drops on DOM elements with 'yourDropTarget' CSS class specified
dropTargetSelector : '.yourDropTarget',
// Only allow dragging elements with the 'draggable' CSS class
targetSelector : '.draggable'
}
construct(config) {
const me = this;
super.construct(config);
me.on({
dragstart : me.onDragStart
});
}
onDragStart({ event, context }) {
const target = context.target;
// Here you identify what you are dragging (an image of a user, grid row in an order table etc) and map it to something in your
// data model. You can store your data on the context object which is available to you in all drag-related events
context.userId = target.dataset.userId;
}
onEquipmentDrop({ context, event }) {
const me = this;
if (context.valid) {
const userId = context.userId,
droppedOnTarget = context.target;
console.log(`You dropped user ${userStore.getById(userId).name} on ${droppedOnTarget}`, droppedOnTarget);
// Dropped on a scheduled event, display toast
Toast.show(`You dropped user ${userStore.getById(userId).name} on ${droppedOnTarget}`);
}
}
};
Dragging multiple elements
You can tell the DragHelper to also move additional relatedElements when a drag operation is starting. Simply
provide an array of elements on the context object:
new DragHelper ({
callOnFunctions : true,
onDragStart({ context }) {
// Let drag helper know about extra elements to drag
context.relatedElements = Array.from(element.querySelectorAll('.b-resource-avatar'));
}
});
Creating a custom drag proxy
Using the createProxy you can create any markup structure to use when dragging cloned targets.
new DragHelper ({
callOnFunctions : true,
// Don't drag the actual cell element, clone it
cloneTarget : true,
// We size the cloned element using CSS
autoSizeClonedTarget : false,
mode : 'translateXY',
// Only allow drops on certain DOM nodes
dropTargetSelector : '.myDropTarget',
// Only allow dragging cell elements in a Bryntum Grid
targetSelector : '.b-grid-row:not(.b-group-row) .b-grid-cell'
// Here we receive the element where the drag originated and we can choose to return just a child element of it
// to use for the drag proxy (such as an icon)
createProxy(element) {
return element.querySelector('i').cloneNode();
}
});
Animating a cloned drag proxy to a point before finalizing
To provide users with the optimal user experience, you can set a transitionTo object (with target element and
align spec) on the DragHelper´s context object inside a drop listener (only applies to translate
mode operations). This will trigger a final animation of the drag proxy which should represent
the change of data state that will be triggered by the drop.
You can see this in action in Gantt´s drag-resource-from-grid demo.
new DragHelper ({
callOnFunctions : true,
// Don't drag the actual cell element, clone it
cloneTarget : true,
// We size the cloned element using CSS
autoSizeClonedTarget : false,
mode : 'translateXY',
// Only allow drops on certain DOM nodes
dropTargetSelector : '.myDropTarget',
// Only allow dragging cell elements in a Bryntum Grid
targetSelector : '.b-grid-row:not(.b-group-row) .b-grid-cell'
// Here we receive the element where the drag originated and we can choose to return just a child element of it
// to use for the drag proxy (such as an icon)
createProxy(element) {
return element.querySelector('i').cloneNode();
},
async onDrop({ context, event }) {
// If it's a valid drop, provide a point to animate the proxy to before finishing the operation
if (context.valid) {
await this.animateProxyTo(someElement, {
// align left side of drag proxy to right side of the someElement
align : 'l0-r0'
});
}
else {
Toast.show(`You cannot drop here`);
}
}
});
Configs
33
Configs
33Common
Other
Set to false to not apply width/height of cloned drag proxy elements.
Set to true to clone the dragged target, and not move the actual target DOM node.
Constrain translate drag to dragWithin elements bounds (set to false to allow it to "overlap" edges)
Containers whose elements can be rearranged (and moved between the containers). Used when mode is set to "container".
Creates the proxy element to be dragged, when using cloneTarget. Clones the original element by default. Provide your custom createProxy function to be used for creating drag proxy.
| Parameter | Type | Description |
|---|---|---|
element | HTMLElement | The element from which the drag operation originated |
The amount of pixels to move mouse before it counts as a drag operation
Outer element that limits where element can be dragged
A CSS selector added to each drop target element while dragging.
A CSS selector used to determine if a drop is allowed at the current position.
Set to true to hide the original element while dragging (applicable when cloneTarget is true).
A CSS selector used to exclude elements when using container mode
CSS class added when drag is invalid
A function that determines if dragging an element is allowed. Gets called with the element as argument,
return true to allow dragging or false to prevent.
| Parameter | Type | Description |
|---|---|---|
element | HTMLElement |
Configure as true to disallow dragging in the X axis. The dragged element will only move vertically.
Configure as true to disallow dragging in the Y axis. The dragged element will only move horizontally.
Largest allowed x when dragging horizontally.
Largest allowed y when dragging horizontally.
Smallest allowed x when dragging horizontally.
Smallest allowed y when dragging horizontally.
Enabled dragging, specify mode:
| container | Allows reordering elements within one and/or between multiple containers |
| translateXY | Allows dragging within a parent container |
The outer element where the drag helper will operate (attach events to it and use as outer limit when looking for ancestors)
A CSS selector used to target a child element of the mouse down element, to use as the drag proxy element. Applies to translate mode when using cloneTarget.
Configure as false to take ownership of the proxy element after a valid drop (advanced usage).
Scroll manager of the target. If specified, scrolling while dragging is supported.
A Widget or HTML element to drag. See also targetSelector.
A CSS selector used to target draggable elements. See also target.
The amount of milliseconds to wait after a touchstart, before a drag gesture will be allowed to start.
When using unifiedProxy, use this amount of pixels to offset each extra element when dragging multiple items
Set to true to stack any related dragged elements below the main drag proxy element. Only applicable when
using translate mode with cloneTarget
Properties
10
Properties
10Class hierarchy
Other
Returns true if a drag operation is active
Misc
Functions
27
Functions
27Other
Abort dragging
Animated the proxy element to be aligned with the passed element. Returns a Promise which resolves after the DOM transition completes. Only applies to 'translateXY' mode.
| Parameter | Type | Description |
|---|---|---|
element | HTMLElement | Rectangle | The target element or a Rectangle |
alignSpec | AlignSpec | An object describing how to the align drag proxy to the target element to offset the aligned widget further from the target. May be configured as -ve to move the aligned widget towards the target - for example producing the effect of the anchor pointer piercing the target. |
Initializes a new DragHelper.
| Parameter | Type | Description |
|---|---|---|
config | DragHelperConfig | Configuration object, accepts options specified under Configs above |
Creates the proxy element to be dragged, when using cloneTarget. Clones the original element by default. Override it to provide your own custom HTML element structure to be used as the drag proxy.
| Parameter | Type | Description |
|---|---|---|
element | HTMLElement | The element from which the drag operation originated |
Configuration
Events
Events
8
Events
8Fired after a drop at an invalid position
// Adding a listener using the "on" method
dragHelper.on('abort', ({ source, context, context.element, context.target, context.grabbed, context.relatedElements, event }) => {
});| Parameter | Type | Description |
|---|---|---|
source | DragHelper | |
context | Object | |
context.element | HTMLElement | The element which we are moving, could be a cloned version of grabbed, or the grabbed element itself |
context.target | HTMLElement | The target element below the cursor |
context.grabbed | HTMLElement | The original element upon which the mousedown event triggered a drag operation |
context.relatedElements | HTMLElement[] | An array of extra elements dragged with the main dragged element |
event | MouseEvent |
Fired before dragging starts, return false to prevent the drag operation.
// Adding a listener using the "on" method
dragHelper.on('beforeDragStart', ({ source, context, context.element, event }) => {
});| Parameter | Type | Description |
|---|---|---|
source | DragHelper | |
context | Object | |
context.element | HTMLElement | The original element upon which the mousedown event triggered a drag operation |
event | MouseEvent | TouchEvent |
Fired while dragging, you can signal that the drop is valid or invalid by setting context.valid = false;
// Adding a listener using the "on" method
dragHelper.on('drag', ({ source, context, context.element, context.target, context.grabbed, context.newX, context.newY, context.relatedElements, context.valid, event }) => {
});| Parameter | Type | Description |
|---|---|---|
source | DragHelper | |
context | Object | |
context.element | HTMLElement | The element which we are moving, could be a cloned version of grabbed, or the grabbed element itself |
context.target | HTMLElement | The target element below the cursor |
context.grabbed | HTMLElement | The original element upon which the mousedown event triggered a drag operation |
context.newX | Number | The new X page coordinate of the dragged element |
context.newY | Number | The new Y page coordinate of the dragged element |
context.relatedElements | HTMLElement[] | An array of extra elements dragged with the main dragged element |
context.valid | Boolean | Set this to true or false to indicate whether the drop position is valid. |
event | MouseEvent |
Fired when dragging starts. The event includes a context object. If you want to drag additional elements you can
provide these as an array of elements assigned to the relatedElements property of the context object.
// Adding a listener using the "on" method
dragHelper.on('dragStart', ({ source, context, context.element, context.grabbed, context.relatedElements, event }) => {
});| Parameter | Type | Description |
|---|---|---|
source | DragHelper | |
context | Object | |
context.element | HTMLElement | The element which we're moving, could be a cloned version of grabbed, or the grabbed element itself |
context.grabbed | HTMLElement | The original element upon which the mousedown event triggered a drag operation |
context.relatedElements | HTMLElement[] | Array of extra elements to include in the drag. |
event | MouseEvent | TouchEvent |
Fires after drop. For valid drops, it exposes context.async which you can set to true to signal that additional
processing is needed before finalizing the drop (such as showing some dialog). When that operation is done, call
context.finalize(true/false) with a boolean that determines the outcome of the drop.
You can signal that the drop is valid or invalid by setting context.valid = false;
For translate type drags with cloneTarget, you can also set transitionTo if you want to animate
the dragged proxy to a position before finalizing the operation. See class intro text for example usage.
// Adding a listener using the "on" method
dragHelper.on('drop', ({ source, context, context.element, context.target, context.grabbed, context.relatedElements, context.valid }) => {
});| Parameter | Type | Description |
|---|---|---|
source | DragHelper | |
context | Object | |
context.element | HTMLElement | The element which we are moving, could be a cloned version of grabbed, or the grabbed element itself |
context.target | HTMLElement | The target element below the cursor |
context.grabbed | HTMLElement | The original element upon which the mousedown event triggered a drag operation |
context.relatedElements | HTMLElement[] | An array of extra elements dragged with the main dragged element |
context.valid | Boolean | true if the drop position is valid |
Event handlers
8
Event handlers
8Called after a drop at an invalid position
new DragHelper({
onAbort({ source, context, event }) {
}
});| Parameter | Type | Description |
|---|---|---|
source | DragHelper | |
context | Object | |
context.element | HTMLElement | The element which we are moving, could be a cloned version of grabbed, or the grabbed element itself |
context.target | HTMLElement | The target element below the cursor |
context.grabbed | HTMLElement | The original element upon which the mousedown event triggered a drag operation |
context.relatedElements | HTMLElement[] | An array of extra elements dragged with the main dragged element |
event | MouseEvent |
Called before dragging starts, return false to prevent the drag operation.
new DragHelper({
onBeforeDragStart({ source, context, event }) {
}
});| Parameter | Type | Description |
|---|---|---|
source | DragHelper | |
context | Object | |
context.element | HTMLElement | The original element upon which the mousedown event triggered a drag operation |
event | MouseEvent | TouchEvent |
Called while dragging, you can signal that the drop is valid or invalid by setting context.valid = false;
new DragHelper({
onDrag({ source, context, event }) {
}
});| Parameter | Type | Description |
|---|---|---|
source | DragHelper | |
context | Object | |
context.element | HTMLElement | The element which we are moving, could be a cloned version of grabbed, or the grabbed element itself |
context.target | HTMLElement | The target element below the cursor |
context.grabbed | HTMLElement | The original element upon which the mousedown event triggered a drag operation |
context.newX | Number | The new X page coordinate of the dragged element |
context.newY | Number | The new Y page coordinate of the dragged element |
context.relatedElements | HTMLElement[] | An array of extra elements dragged with the main dragged element |
context.valid | Boolean | Set this to true or false to indicate whether the drop position is valid. |
event | MouseEvent |
Called when dragging starts. The event includes a context object. If you want to drag additional elements you can
provide these as an array of elements assigned to the relatedElements property of the context object.
new DragHelper({
onDragStart({ source, context, event }) {
}
});| Parameter | Type | Description |
|---|---|---|
source | DragHelper | |
context | Object | |
context.element | HTMLElement | The element which we're moving, could be a cloned version of grabbed, or the grabbed element itself |
context.grabbed | HTMLElement | The original element upon which the mousedown event triggered a drag operation |
context.relatedElements | HTMLElement[] | Array of extra elements to include in the drag. |
event | MouseEvent | TouchEvent |
Called after drop. For valid drops, it exposes context.async which you can set to true to signal that additional
processing is needed before finalizing the drop (such as showing some dialog). When that operation is done, call
context.finalize(true/false) with a boolean that determines the outcome of the drop.
You can signal that the drop is valid or invalid by setting context.valid = false;
For translate type drags with cloneTarget, you can also set transitionTo if you want to animate
the dragged proxy to a position before finalizing the operation. See class intro text for example usage.
new DragHelper({
onDrop({ source, context }) {
}
});| Parameter | Type | Description |
|---|---|---|
source | DragHelper | |
context | Object | |
context.element | HTMLElement | The element which we are moving, could be a cloned version of grabbed, or the grabbed element itself |
context.target | HTMLElement | The target element below the cursor |
context.grabbed | HTMLElement | The original element upon which the mousedown event triggered a drag operation |
context.relatedElements | HTMLElement[] | An array of extra elements dragged with the main dragged element |
context.valid | Boolean | true if the drop position is valid |