DEMOS
DOCS
FEATURES
DOWNLOAD
PURCHASE
CONTACT
BLOG

Drag/Drop Manager

A common use case in the sorts of applications for which the Toolkit is useful is the requirement to be able to drag and drop new nodes/groups onto the workspace. Surface widgets already support this concept via the registerDroppableNodes method, but the Drop Manager, an optional new include from version 1.13.1 onwards, offers a more flexible approach to achieving the same goal.

Briefly, the Drop Manager offers:

  • the ability to drag objects onto the whitespace of the canvas
  • the ability to drag object onto groups or nodes
  • the ability to drag objects onto edges
  • the ability to disable the drag/drop functionality programmatically

The Surface's registerDroppableNodes method (discussed here )offers the first two of these points, but not the second two, and, importantly, it requires either that the draggable nodes be present in the DOM at the time that the method is called, or that you refresh the handle returned from the method if you make changes to the DOM and you wish the drag/drop functionality to track those changes. The Drop Manager attaches a delegated event handler to the object containing items you wish to drag, and does not need to be refreshed when changes are made.

At some point in the future, registerDroppableNodes will be deprecated on the Surface widget, and eventually removed.


Installation

The Drop Manager ships in a separate package to the main Toolkit code. If you're using npm to manage your jsPlumb dependencies, you need to add an extra line to package.json:

    "jsplumbtoolkit-drop":"file:path/to/jsplumbtoolkit-drop.tgz"

The Drop Manager is also available in js/jsplumbtoolkit-drop.js in the licensed package.

To import the class you need:

import { jsPlumbToolkitDropManager } from "jsplumbtoolkit-drop"

TOP


Basic Example

new jsPlumbToolkitDropManager({    
    surface:SomeSurfaceWidget,
    source:someHTMLElementContainingDraggableChildren,
    selector:".draggable-child",
    onDrop:function(data, target, draggedElement, event, position) {
        console.log("drop on node or group", arguments);
    },
    onCanvasDrop:function(data, canvasPosition, draggedElement, event, position) {
        console.log("drop on canvas", arguments);    
    },
   onEdgeDrop:function(data, target, draggedElement, event, position) {
       console.log("drop on edge", arguments);
   }   
})

The arguments here are:

  • surface Required. Identifies the Surface widget with which to interact
  • source Required. Identifies the DOM element inside which the Drop Manager will find draggable elements
  • selector Required. Identifies the child elements inside source that are draggable.
  • onDrop Optional callback to hit when the user drops an element on a node or group
  • onCanvasDrop Optional callback to hit when the user drops an element on whitespace in the canvas
  • onEdgeDrop Optional callback to hit when the user drops an element on an edge

In this setup, objects can be dropped on nodes, groups, edges, and the whitespace of the canvas.

TOP


Associating data with the dragged object

When drag starts, you can generate some data for the Drop Manager to associate with the object being dragged. You do this with a dataGenerator function:

new jsPlumbToolkitDropManager({    
    ...
    dataGenerator:function(el) {
        return {type: el.getAttribute("data-type") }
    },
    ...
})

Our dataGenerator here extracts the value of the data-type attribute on the element being dragged, and returns it in an object. The return value of the dataGenerator is what is passed in as the data argument to the various drop methods.

The signature of the dataGenerator function is:

export type DataGeneratorFunction<T> = (el:HTMLElement) => T;

TOP


Controlling drop targets

You can control which parts of the Surface act as drop targets. Here we'll disable everything except dropping on edges:

new jsPlumbToolkitDropManager({    
    surface:SomeSurfaceWidget,
    source:someHTMLElementContainingDraggableChildren,
    selector:".draggable-child",
    allowDropOnNodesOrGroups:false,
    allowDropOnCanvas:false,
    allowDropOnEdges:true,
    dataGenerator:function(el) {
        return {type: el.getAttribute("data-type") }
    },
    onEdgeDrop:function(data, target, draggedElement, event, position) {
       console.log("drop on edge", arguments);
    }   
})

Note that the default value for any allowDropOn*** flag is true, so in this case we did not need to include allowDropOnEdges:true, we just did it to show you the flag.

TOP


Filtering drop targets

In addition to setting any of the allowDropOn*** flags, you can use filters to exclude elements at drag time. In this example we'll filter out any node/group that has foo:true in its data:

new jsPlumbToolkitDropManager({    
    surface:SomeSurfaceWidget,
    source:someHTMLElementContainingDraggableChildren,
    selector:".draggable-child",
    allowDropOnCanvas:false,
    allowDropOnEdges:false,
    dropFilter:function(data, nodeOrGroup) {
        return nodeOrGroup.data.foo !== true;    
    },
    onDrop:function(data, target, draggedElement, event, position) {
        console.log("drop on node or group", arguments);
    }  
})

The method signature for dropFilter is:

export type DropFilter<T> = (data:T, target:Node|Group) => boolean;

It is also possible, when drag starts, to decide whether or not you want the dragged element to be droppable on the canvas:

new jsPlumbToolkitDropManager({    
    surface:SomeSurfaceWidget,
    source:someHTMLElementContainingDraggableChildren,
    selector:".draggable-child",
    allowDropOnCanvas:false,
    allowDropOnEdges:false,
    canvasDropFilter:function(data) {
        return data.type === "someDroppableOnCanvasType";    
    },
    onDrop:function(data, canvasPosition, draggedElement, event, position) {
        console.log("drop on canvas", arguments);
    }  
})

The method signature for the canvasDropFilter is:

export type CanvasDropFilter<T> = (data:T) => boolean;

And of course you can also filter by edge:

new jsPlumbToolkitDropManager({    
    surface:SomeSurfaceWidget,
    source:someHTMLElementContainingDraggableChildren,
    selector:".draggable-child",
    edgeDropFilter:function(data, edge) {
        return edge.data.foo !== true;    
    },
      ...
})

The method signature for edgeDropFilter is:

export type EdgeDropFilter<T> = (data:T, target:Edge) => boolean;

TOP


Enabling/Disabling the Drop Manager

You can disable/enable the entire Drop Manager at any time:

dropManager.setEnabled(false);

TOP


CSS

There are two CSS classes that are assigned to parts of the UI during the lifecycle of a drag:

class>defaultpurpose
dragActiveClassjtk-drag-drop-activeAssigned to any part of the UI that is a target for a drop of the current element
dragHoverClassjtk-drag-drop-hoverAssigned to any drop target over which the current element is hovering. When the mouse is released the element having this class will be the recipient of an on drop event.

You can provide your own values for these in the Drop Manager constructor:

new jsPlumbToolkitDropManager({
    dragActiveClass:"drag-active",
    dragHoverClass:"you-can-drop-here"
});

In order to use these classes for visual cues in the UI, you'll probably want to define slightly different selectors for each target type. Let's suppose when a drag starts we want to outline our canvas and any nodes/groups with a purple line, and we want to draw any possible target edges with a purple line too:

.jtk-surface.jtk-drag-drop-active, .jtk-node.jtk-drag-drop-active, .jtk-group.jtk-drag-drop-active {
    outline:4px solid purple;
}

svg.jtk-drag-drop-active path {
    stroke:purple;
}

Now when something is the current drop target, we'll either outline it green or make its path green:

.jtk-surface.jtk-drag-drop-hover, .jtk-node.jtk-drag-drop-hover, .jtk-group.jtk-drag-drop-hover {
    outline:4px solid green;
}

svg.jtk-drag-drop-hover path {
    stroke:green;
}

This is just an example of course. You can do anything with the CSS that you like.

TOP


Usage from within Typescript

The Drop Manager takes a type parameter T that identifies the type of data that your dataGenerator function is going to return. You'll see the type T listed in various function definitions above; if you are not using Typescript then this type parameter disappears and you don't need to think about it.