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 relatedElements along with the main element
  • Firing useful events beforeDragStart, dragStart, drag, drop, abort
  • Validation by setting a valid Boolean 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

Drag helper
//<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 containers
  • translateXY - 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

Common

listenersEvents

Other

autoSizeClonedTarget: Boolean= true

Set to false to not apply width/height of cloned drag proxy elements.

cloneTarget: Boolean= false

Set to true to clone the dragged target, and not move the actual target DOM node.

constrain: Boolean= true

Constrain translate drag to dragWithin elements bounds (set to false to allow it to "overlap" edges)

containers: HTMLElement[]

Containers whose elements can be rearranged (and moved between the containers). Used when mode is set to "container".

createProxy: function

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.

ParameterTypeDescription
elementHTMLElement

The element from which the drag operation originated

Returns: HTMLElement
dragThreshold: Number= 5

The amount of pixels to move mouse before it counts as a drag operation

dragWithin: HTMLElement

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.

hideOriginalElement: Boolean= false

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

invalidCls: String= b-drag-invalid

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.

ParameterTypeDescription
elementHTMLElement
Returns: Boolean
lockX: Boolean= false

Configure as true to disallow dragging in the X axis. The dragged element will only move vertically.

lockY: Boolean= false

Configure as true to disallow dragging in the Y axis. The dragged element will only move horizontally.

maxX: Number

Largest allowed x when dragging horizontally.

maxY: Number

Largest allowed y when dragging horizontally.

minX: Number

Smallest allowed x when dragging horizontally.

minY: Number

Smallest allowed y when dragging horizontally.

mode: container | translateXY= translateXY

Enabled dragging, specify mode:

containerAllows reordering elements within one and/or between multiple containers
translateXYAllows dragging within a parent container
outerElement: HTMLElement

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.

removeProxyAfterDrop: Boolean= true

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.

target: HTMLElement | Widget

A Widget or HTML element to drag. See also targetSelector.

A CSS selector used to target draggable elements. See also target.

touchStartDelay: Number= 200

The amount of milliseconds to wait after a touchstart, before a drag gesture will be allowed to start.

unifiedOffset: Number= 5

When using unifiedProxy, use this amount of pixels to offset each extra element when dragging multiple items

unifiedProxy: Boolean

Set to true to stack any related dragged elements below the main drag proxy element. Only applicable when using translate mode with cloneTarget

Misc

Properties

10

Class hierarchy

isDragHelper: Boolean= truereadonly
Identifies an object as an instance of DragHelper class, or subclass thereof.
isDragHelper: Boolean= truereadonlystatic
Identifies an object as an instance of DragHelper class, or subclass thereof.
isEventsEvents

Other

isDragging: Booleanreadonly

Returns true if a drag operation is active

Lifecycle

configBase

Misc

Functions

27

Other

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.

ParameterTypeDescription
elementHTMLElement | Rectangle

The target element or a Rectangle

alignSpecAlignSpec

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.

ParameterTypeDescription
configDragHelperConfig

Configuration object, accepts options specified under Configs above

new DragHelper({
  containers: [div1, div2],
  isElementDraggable: element => element.className.contains('handle'),
  outerElement: topParent,
  listeners: {
    drop: onDrop,
    thisObj: this
  }
});

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.

ParameterTypeDescription
elementHTMLElement

The element from which the drag operation originated

Returns: HTMLElement
onEvents
relayAllEvents
triggerEvents
unEvents

Configuration

applyDefaultsstaticBase

Events

Lifecycle

destroystaticBase

Misc

initClassstaticBase
isOfTypeNamestaticBase
mixinstaticBase

Events

8

Fired 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 }) => {

});
ParameterTypeDescription
sourceDragHelper
contextObject
context.elementHTMLElement

The element which we are moving, could be a cloned version of grabbed, or the grabbed element itself

context.targetHTMLElement

The target element below the cursor

context.grabbedHTMLElement

The original element upon which the mousedown event triggered a drag operation

context.relatedElementsHTMLElement[]

An array of extra elements dragged with the main dragged element

eventMouseEvent

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 }) => {

});
ParameterTypeDescription
sourceDragHelper
contextObject
context.elementHTMLElement

The original element upon which the mousedown event triggered a drag operation

eventMouseEvent | 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 }) => {

});
ParameterTypeDescription
sourceDragHelper
contextObject
context.elementHTMLElement

The element which we are moving, could be a cloned version of grabbed, or the grabbed element itself

context.targetHTMLElement

The target element below the cursor

context.grabbedHTMLElement

The original element upon which the mousedown event triggered a drag operation

context.newXNumber

The new X page coordinate of the dragged element

context.newYNumber

The new Y page coordinate of the dragged element

context.relatedElementsHTMLElement[]

An array of extra elements dragged with the main dragged element

context.validBoolean

Set this to true or false to indicate whether the drop position is valid.

eventMouseEvent

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 }) => {

});
ParameterTypeDescription
sourceDragHelper
contextObject
context.elementHTMLElement

The element which we're moving, could be a cloned version of grabbed, or the grabbed element itself

context.grabbedHTMLElement

The original element upon which the mousedown event triggered a drag operation

context.relatedElementsHTMLElement[]

Array of extra elements to include in the drag.

eventMouseEvent | 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 }) => {

});
ParameterTypeDescription
sourceDragHelper
contextObject
context.elementHTMLElement

The element which we are moving, could be a cloned version of grabbed, or the grabbed element itself

context.targetHTMLElement

The target element below the cursor

context.grabbedHTMLElement

The original element upon which the mousedown event triggered a drag operation

context.relatedElementsHTMLElement[]

An array of extra elements dragged with the main dragged element

context.validBoolean

true if the drop position is valid

catchAllEvents
destroyEvents

Event handlers

8

Called after a drop at an invalid position

new DragHelper({
    onAbort({ source, context, event }) {

    }
});
ParameterTypeDescription
sourceDragHelper
contextObject
context.elementHTMLElement

The element which we are moving, could be a cloned version of grabbed, or the grabbed element itself

context.targetHTMLElement

The target element below the cursor

context.grabbedHTMLElement

The original element upon which the mousedown event triggered a drag operation

context.relatedElementsHTMLElement[]

An array of extra elements dragged with the main dragged element

eventMouseEvent

Called before dragging starts, return false to prevent the drag operation.

new DragHelper({
    onBeforeDragStart({ source, context, event }) {

    }
});
ParameterTypeDescription
sourceDragHelper
contextObject
context.elementHTMLElement

The original element upon which the mousedown event triggered a drag operation

eventMouseEvent | 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 }) {

    }
});
ParameterTypeDescription
sourceDragHelper
contextObject
context.elementHTMLElement

The element which we are moving, could be a cloned version of grabbed, or the grabbed element itself

context.targetHTMLElement

The target element below the cursor

context.grabbedHTMLElement

The original element upon which the mousedown event triggered a drag operation

context.newXNumber

The new X page coordinate of the dragged element

context.newYNumber

The new Y page coordinate of the dragged element

context.relatedElementsHTMLElement[]

An array of extra elements dragged with the main dragged element

context.validBoolean

Set this to true or false to indicate whether the drop position is valid.

eventMouseEvent

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 }) {

    }
});
ParameterTypeDescription
sourceDragHelper
contextObject
context.elementHTMLElement

The element which we're moving, could be a cloned version of grabbed, or the grabbed element itself

context.grabbedHTMLElement

The original element upon which the mousedown event triggered a drag operation

context.relatedElementsHTMLElement[]

Array of extra elements to include in the drag.

eventMouseEvent | 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 }) {

    }
});
ParameterTypeDescription
sourceDragHelper
contextObject
context.elementHTMLElement

The element which we are moving, could be a cloned version of grabbed, or the grabbed element itself

context.targetHTMLElement

The target element below the cursor

context.grabbedHTMLElement

The original element upon which the mousedown event triggered a drag operation

context.relatedElementsHTMLElement[]

An array of extra elements dragged with the main dragged element

context.validBoolean

true if the drop position is valid

onDestroyEvents

Typedefs

1