Skip to main content

Drag and drop

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.

The Toolkit ships with the @jsplumbtoolkit/drop package, which contains two classes - DropManager, a low level drag/drop manager, and SurfaceDropManager, an extension of the drop manager that makes integrating it with a Surface a breeze. The difference between the two classes is that DropManager provides a means for you to respond to items being dropped onto a surface canvas, but it does not do anything when such an event occurs other than inform you, via a set of callbacks, whereas the SurfaceDropManager provides implementations of these callbacks that manipulate the canvas and the data model. For the vast majority of applications the SurfaceDropManager will be the class you'll want to use.

Installation

"dependencies":{
...,
"@jsplumbtoolkit/drop":"^5.0.0"
...

Surface Drop Manager

Instantiation

At the bare minimum, you need to provide these parameters when you create an instance of the Surface Drop Manager:

import { createSurfaceManager } from "@jsplumbtoolkit/drop"

createSurfaceManager({
source:someElement,
selector:"[data-node-type]",
surface:renderer,
dataGenerator: (el) => {
return {
name:el.getAttribute("data-node-type"),
type:el.getAttribute("data-node-type")
};
}
});

  • source The element containing other elements that are draggable
  • selector a CSS3 selector identifying elements within source that are draggable
  • surface The Surface to attach to
  • dataGenerator A function that can return an appropriate data object representing some element that is being dragged.

Options

The full set of options available are:

Home > @jsplumbtoolkit/drop > SurfaceDropManagerOptions

SurfaceDropManagerOptions interface

Options for the SurfaceDropManager.

Signature:

export interface SurfaceDropManagerOptions<T> 

Properties

PropertyModifiersTypeDescription
allowDropOnCanvas?boolean(Optional) Defaults to true. Allows items to be dropped onto whitespace.
allowDropOnEdge?boolean(Optional) Defaults to true. Allows items to be dropped onto edges in the canvas.
allowDropOnGroup?boolean(Optional) Defaults to true. Allows items to be dropped onto groups in the canvas.
allowDropOnNode?boolean(Optional) Defaults to false. Allows items to be dropped onto nodes in the canvas. If this is true and an element is dropped onto a node, the result is the same as if the element has been dropped onto whitespace.
canvasSelector?string(Optional) Optional selector specifying what parts of the surface's canvas should be considered whitespace. If you're using a decorator, for instance, you might want to add a selector for that decorator's elements so that items can be dropped onto them.
dataGenerator?DataGeneratorFunction<T>(Optional) Optional function to generate an initial payload from an element that has started to be dragged.
elementGenerator?(el: Element) => Element(Optional)
groupIdentifier?GroupIdentifierFunction<T>(Optional) Optional function to use to determine if the element being dragged represents a group. If you do not provide this, the default behaviour is to check for the presence of a data-jtk-is-group attribute on the element, with a value of true.
ignoreConstrainFunction?boolean(Optional) If true, the manager will ignore any element constrain function that may be set on the surface
ignoreGrid?boolean(Optional) Defaults to false. By default this class will conform to any grid in place in the surface to which it is attached when dragging items around.
ignoreZoom?boolean(Optional) By default, the SurfaceDropManager will apply a scale transform to elements that are being dragged so that they appear at the same size as the Surface they're being dragged to. Setting this flag to true will switch off that behaviour.
magnetize?boolean(Optional) If true, the surface will be instructed to magnetize after dropping a new element.
onVertexAdded?(v: Vertex) => any(Optional) Optional callback that will be invoked after a new vertex has been dropped and added to the dataset.
selectorstringA CSS selector identifying children of source that are draggable
sourceElementThe element containing things that will be dragged.
surfaceSurfaceThe surface to attach to.
typeGenerator?TypeGeneratorFunction<T>(Optional) Optional function to determine the type of the data object being dragged from an element that has started to be dragged.

By default, the Surface Drop Manager is configured to allow nodes/groups to be dropped onto the canvas or an existing edge, and for nodes to be dropped on groups. You can control this via the appropriate allowDropOn*** flags.

Providing data for a dragged element

The dataGenerator function is used to get a suitable piece of backing data for some element that is being dragged. In the example above (which comes from the Database Visualiser demo), we provide a data object with name and type properties. The default mechanism used by the Toolkit for determining the type of some object is to test the type member. Providing type as we have here allows the Toolkit to determine which template to use to render the node, how it will behave, etc.

The method signature for dataGenerator is:

Home > @jsplumbtoolkit/drop > DataGeneratorFunction

DataGeneratorFunction type

Defines the function that is invoked to gather a dataset to associate with an item that is being dragged.

Signature:

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

Specifying a dragged element's type

As mentioned, the Toolkit's default mechanism for determining the type of some object is to look at its type member. If you wish, you can provide your own typeGenerator function for the Surface Drop Manager to use.

The method signature for typeGenerator is:

Home > @jsplumbtoolkit/drop > TypeGeneratorFunction

TypeGeneratorFunction type

Defines the function invoked to determine the type of some item that is about to be dragged.

Signature:

export declare type TypeGeneratorFunction<T> = (d: T) => string;

Distinguishing between a node and a group

By default, the Toolkit will look for a jtk-is-group attribute on a dragged element. If the value of this attribute is "true", the Toolkit will assume the element represents a group. You can provide your own groupIdentifier if you wish; the signature is as shown above.

The method signature for groupIdentifier is:

Home > @jsplumbtoolkit/drop > GroupIdentifierFunction

GroupIdentifierFunction type

Defines the function that is invoked to determine whether an item that is about to be dragged represents a group.

Signature:

export declare type GroupIdentifierFunction<T> = (d: T, el: Element) => boolean;

Magnetizing the UI when dropping on an edge

By default, the Toolkit will "magnetize" the UI when a node or group is inserted between two existing nodes/groups. You can switch this off by setting magnetize:false.

Enabling/disabling

Use the setEnabled(enabled:boolean) method on a SurfaceDropManager to enable/disable it.

Library Integrations

Wrappers for the Surface Drop Manager are available for:

For Svelte no wrapper library is needed.


Drop manager

The Drop Manager provides a superset of the functionality that the Surface Drop Manager offers: you can configure a drag source in the same way, but the Drop Manager does not manipulate the dataset in any way when an event occurs - it just hits an appropriate callback function with the event details.

Basic Example

import { createManager } from "@jsplumbtoolkit/drop"

const dropManager = createManager({
surface:SomeSurfaceWidget,
source:someElement,
selector:".draggable-child",
onDrop:(data, target, draggedElement, event, position) => {
console.log("drop on node or group", arguments);
},
onCanvasDrop:(data, canvasPosition, draggedElement, event, position) => {
console.log("drop on canvas", arguments);
},
onEdgeDrop:(data, target, draggedElement, event, position) => {
console.log("drop on edge", arguments);
},
dataGenerator:(el) => {
return {type: el.getAttribute("data-type") }
}
})

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
  • dataGenerator A function that can return an appropriate data object representing some element that is being dragged.

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

Options

The full set of options available are:

Home > @jsplumbtoolkit/drop > DropManagerOptions

DropManagerOptions interface

Options for the DropManager.

Signature:

export interface DropManagerOptions<T> 

Properties

PropertyModifiersTypeDescription
canvasDropFilter?CanvasDropFilter<T>(Optional)
canvasSelector?string(Optional)
dataGeneratorDataGeneratorFunction<T>
dragActiveClass?string(Optional)
dragHoverClass?string(Optional)
dropFilter?DropFilter<T>(Optional)
edgeDropFilter?EdgeDropFilter<T>(Optional)
elementGenerator?(el: Element) => Element(Optional)
enabled?boolean(Optional)
ignoreConstrainFunction?boolean(Optional) If true, the manager will ignore any element constrain function that may be set on the surface
ignoreGrid?boolean(Optional)
ignoreZoom?boolean(Optional) By default, the DropManager will apply a scale transform to elements that are being dragged so that they appear at the same size as the Surface they're being dragged to. Setting this flag to true will switch off that behaviour.
onCanvasDrop?CanvasDropFunction<T>(Optional)
onDrag?DragFunction<T>(Optional)
onDrop?DropFunction<T>(Optional)
onEdgeDrop?EdgeDropFunction<T>(Optional)
scope?string(Optional)
selectorstringA selector that identifies the draggable elements inside source.
sourceElementThe element that contains draggable elements.
surfaceSurfaceThe Surface to attach to. Required.

The method signatures for the various on**Drop functions are:

Home > @jsplumbtoolkit/drop > DropFunction

DropFunction type

Defines the function invoked when an item is dropped onto an existing vertex.

Signature:

export declare type DropFunction<T> = (data: T, target: Node | Group, draggedElement?: Element, e?: Event, position?: PointXY, canvasLocation?: PointXY, targetLocation?: PointXY, locationOnTarget?: PointXY) => void;

Home > @jsplumbtoolkit/drop > CanvasDropFunction

CanvasDropFunction type

Defines the function invoked when an item is dropped onto whitespace in the canvas.

Signature:

export declare type CanvasDropFunction<T> = (data: T, canvasPosition: PointXY, draggedElement?: Element, e?: Event, position?: PointXY, elementSize?: Size) => void;

Home > @jsplumbtoolkit/drop > EdgeDropFunction

EdgeDropFunction type

Defines the function invoked when an item is dropped on an edge.

Signature:

export declare type EdgeDropFunction<T> = (data: T, target: Edge, draggedElement?: Element, e?: Event, position?: PointXY, canvasLocation?: PointXY) => void;

Providing data for a dragged element

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:

createManager({    
...
dataGenerator:(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 method signature for dataGenerator is:

Home > @jsplumbtoolkit/drop > DataGeneratorFunction

DataGeneratorFunction type

Defines the function that is invoked to gather a dataset to associate with an item that is being dragged.

Signature:

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

Controlling drop targets

You can control which parts of the Surface act as drop targets by only supplying specific callbacks. Here we'll disable everything except dropping on edges, because we've only supplied an onEdgeDrop callback:

createManager({    
surface:SomeSurfaceWidget,
source:someElement,
selector:".draggable-child",
dataGenerator:(el) => {
return {type: el.getAttribute("data-type") }
},
onEdgeDrop:(data, target, draggedElement, event, position) => {
console.log("drop on edge", arguments);
}
})

Filtering drop targets

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:

createManager({    
surface:SomeSurfaceWidget,
source:someElement,
selector:".draggable-child",
dropFilter:(data, nodeOrGroup) => {
return nodeOrGroup.data.foo !== true;
},
onDrop:(data, target, draggedElement, event, position) => {
console.log("drop on node or group", arguments);
}
})

The method signature for dropFilter is:

Home > @jsplumbtoolkit/drop > DropFilter

DropFilter type

Defines a function used to filter drop on another vertex.

Signature:

export declare 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:

createDropManager({    
surface:SomeSurfaceWidget,
source:someElement,
selector:".draggable-child",
canvasDropFilter:(data) => {
return data.type === "someDroppableOnCanvasType";
},
onDrop:(data, canvasPosition, draggedElement, event, position) => {
console.log("drop on canvas", arguments);
}
})

The method signature for the canvasDropFilter is:

Home > @jsplumbtoolkit/drop > CanvasDropFilter

CanvasDropFilter type

Defines a function used to filter drop on a canvas

Signature:

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

And of course you can also filter by edge:

createManager({    
surface:SomeSurfaceWidget,
source:someElement,
selector:".draggable-child",
edgeDropFilter:(data, edge) => {
return edge.data.foo !== true;
},
...
})

The method signature for edgeDropFilter is:

Home > @jsplumbtoolkit/drop > EdgeDropFilter

EdgeDropFilter type

Defines a function used to filter drop on an edge.

Signature:

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

Enabling/Disabling the Drop Manager

You can disable/enable the entire drop manager at any time:

dropManager.setEnabled(false)

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.

Inserting a node between two nodes on edge drop

This is a use case we were asked about fairly regularly, to the point that we ended up including this functionality in the Surface Drop Manager. For interests sake, we provide here an example onEdgeDrop function that achieves this functionality using the Drop Manager

note

In this example, we are assuming that an Absolute layout is in use, and so the new node's position will be the left and top values we provide. But other layouts will place the new node where they think it ought to go.

onEdgeDrop:(data:any, edge:Edge, 
draggedElement:HTMLElement, evt:Event,
pageLocation:{left:number, top:number}) => {

let positionOnSurface = surface.mapLocation(pageLocation);
toolkit.addFactoryNode(data.type, data,
function(newNode) {
let currentSource = edge.source; // the current source node/port
let currentTarget = edge.target; // the target node/port
toolkit.removeEdge(edge);
toolkit.addEdge({source:currentSource, target:newNode});
toolkit.addEdge({source:newNode, target:currentTarget});
surface.setPosition(newNode, positionOnSurface.left, positionOnSurface.top);
}
);
}

In this example we use the addFactoryNode method to add a new node. This mechanism allows us to generate the data for the new node via our node factory. You could also use addNode(someData) here. We also assume you have a reference to surface - the Surface object you're dropping nodes on - and toolkit, the underlying Toolkit instance.


CSS

There are two CSS classes that are assigned to parts of the UI during the lifecycle of a drag (this applies to both DropManager and SurfaceDropManager)

namedefaultpurpose
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:

createManager({
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.


Working with decorators

If you have any Decorators in your UI, you may wish to inform the drop manager about the elements they have created, because without doing this the drop manager will not be able to recognise them as background. To do this, you use the canvasSelector option:

new jsPlumbToolkitDropManager({    
surface:SomeSurfaceWidget,
source:someElement,
selector:".draggable-child",
canvasSelector:".someElementMyDecoratorCreated",
...
})

canvasSelector takes any valid CSS3 selector. This identifies the elements that your decorator has created that the drop manager should treat as background.