Dragging / Element Dragging

Overview Options CSS Disable Text Selection Setup Dragging Community Edition Setup Dragging Toolkit Edition

A common feature of interfaces using jsPlumb is that the elements are draggable. In the Community Edition you should use the draggable method on a jsPlumbInstance to configure this:

myInstanceOfJsPlumb.draggable("elementId");

...because if you don't, jsPlumb won't know that an element has been dragged, and it won't repaint the element.

In the Toolkit edition, nodes/groups are automatically made draggable by the render call. You can disable that behaviour though, with the elementsDraggable parameter:

var surface = toolkitInstance.render({
  ...
  elementsDraggable:false
});

The options given here are common between the Community and Toolkit editions, but the means of supplying them differs.

You can use the DragOptions default to set default behaviour for dragging. See this page for information about this.

A common request is for the ability to contain the area within which an element may be dragged. jsPlumb offers support for containing a dragged element within the bounds of its parent:

{
   containment:true
}

To constrain elements to a grid, supply the size of the grid in the draggable call:

{
    grid:[50,50]
}

You must set position:absolute on elements that you intend to be draggable. The reasoning for this is that all libraries implement dragging by manipulating the left and top properties of an element's style attribute.

When you position an element absolute, these left/top values are taken to mean with respect to the origin of this element's offsetParent, and the offsetParent is the first ancestor of the element that has position:relative set on it, or the body if no such ancestor is found.

When you position an element relative, the left/top values are taken to mean move the element from its normal position by these amounts, where "normal position" is dependent on document flow. You might have a test case or two in which relative positioning appears to work; for each of these you could create several others where it does not.

You cannot trust relative positioning with dragging.

The jsplumbtoolkit.css file that ships with both the Community and Toolkit editions provides a .jtk-node class which sets position:absolute. The Toolkit edition adds this class to any node it renders; the Community edition will, from 4.x, be adding this class to any element it is requested to manage.


The default browser behaviour on mouse drag is to select elements in the DOM. To assist with handling this, this class is attached to the body at drag start:

jtk-drag-select

The class is removed at drag end.

A suitable value for this class (this is from the jsPlumb demo pages) is:

.jtk-drag-select * {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;    
}    

This CSS rule is provided in the jsplumbtoolkit.css file that is shipped with both the Community and Toolkit editions.


In the Community Edition you supply drag options to the draggable call:

jsPlumb.draggable("someElement", {
    grid:[50,50]
});
Allowed argument types

draggable supports several types as argument:

  • a String representing an element ID
  • an Element
  • any "list-like" object whose contents are Strings or Elements.

A "list-like" element is one that exposes a length property and which can be indexed with brackets ([ ]). Some examples are:

  • arrays
jsPlumbInstance.draggable(["elementOne", "elementTwo"]);
  • jQuery selectors
jsPlumbInstance.draggable($(".someClass"));
  • NodeLists
var els = document.querySelectorAll(".someClass");
jsPlumbInstance.draggable(els);
Manually configuring dragging

If you absolutely cannot use jsPlumb.draggable, you will have to arrange to repaint the drag element manually, via jsPlumb.revalidate.


jsPlumb takes nesting into account when handling draggable elements. For example, say you have this markup:

<div id="container">
  <div class="childType1"></div>
  <div class="childType2"></div>
</div>

...and then you connect one of those child divs to something, and make container draggable:

jsPlumb.connect({
  source:$("#container .childType1"),
  target:"somewhere else"
});

jsPlumb.draggable("container");

Now when you drag container, jsPlumb will have noticed that there are nested elements that have Connections, and they will be updated. Note that the order of operations is not important here: if container was already draggable when you connected one of its children to something, you would get the same result.

Nested Element offsets

For performance reasons, jsPlumb caches the offset of each nested element relative to its draggable ancestor. If you make changes to the draggable ancestor that will have resulted in the offset of one or more nested elements changing, you need to tell jsPlumb about it, using the revalidate function.

Consider the example from before, but with a change to the markup after initializing everything:

<div id="container">
  <div class="header" style="height:20px;background-color:blue;">header</div>
  <div class="childType1"></div>
  <div class="childType2"></div>
<div>

Connect a child div to some element and make it draggable:

jsPlumb.connect({
  source:$("#container .childType1"),
  target:"somewhere else"
});

jsPlumb.draggable($(".childType1"));

Now if you manipulate the DOM in such a way that the internal layout of the container node is changed, you need to tell jsPlumb:

$("#container .header").hide();    // hide the header bar. this will alter the offset of the other child elements...
jsPlumb.revalidate("container");   // tell jsPlumb that the internal dimensions have changed.
// you can also use a selector, eg $("#container")

Note in this example, one of the child divs was made draggable. jsPlumb automatically recalculates internal offsets after dragging stops in that case - you do not need to call it yourself.


The equivalent way to provide drag options in the Toolkit edition is to supply them to the render call:

var surface = toolkitInstance.render({
    ...
    dragOptions:{
        grid:[50,50]
    }
    ...
});

jsPlumb supports dragging multiple elements at once. There are two ways to do this - one via the "drag selection" and the other with a concept called "posses".

Note For users of the Toolkit, the drag selection discussed here maps to the concept of the "current selection" in the Toolkit: when you add a node/group to the current selection, its corresponding DOM element is added to the renderer's drag selection. When you remove a node/group from the current selection, or from the Toolkit itself, its DOM node is removed from the renderer's drag selection. You will not need to know about the methods discussed here.

Every jsPlumb instance has an associated dragSelection - a set of elements that are considered to be "selected" (and which have a CSS class assigned to them to indicate this fact). Any drag event occurring in the instance will cause the currently selected elements to be dragged too.

There are three methods provided to work with the drag selection:

  • addToDragSelection: function (elements) Add the specified elements to the drag selection.
  • removeFromDragSelection: function (elements) Remove the specified elements from the drag selection
  • clearDragSelection Clear the drag selection.

Here, elements can be a single element Id or element, or an array-like object of either of these.

A posse is a group of elements that should all move whenever one of them is dragged. This mechanism is intended to be used for more "permanent" multiple drag arrangements - you may have a set of nodes that should always move together and they do not need to be considered to be "selected".

You can define multiple posses in an instance but each element can belong to one posse only.

Any given element in a Posse has one of two types of membership: either active, in which dragging that element causes the rest of the elements in the Posse to also be dragged, maintaining their positions relative to each other at the start of the drag; or passive, in which the given element can be dragged independently without affecting the other elements in the Posse, but if an active element in that Posse is dragged, the element will be dragged along with it.

Working with Posses is slightly different depending on whether you're using the Toolkit or the Community edition.

Three methods are provided for working wth Posses:

  • addToPosse(elements, posse...)

Here, elements can be a single element Id or element, or an array of either of these (or, in fact, a jQuery selector, since jsPlumb knows that selectors are "list-like"). posse is required, and is a varargs parameter: you can provide more than one. Each posse parameter can either be a String, indicating a Posse to which the element should be added as an active drag participant, or an object containing the Posse's ID and also whether or not the element should be an active drag participant.

For example, in the Flowchart demo, we could make these couple of calls:

jsPlumb.addToPosse(["flowchartWindow1", "flowchartWindow2"], "posse");
jsPlumb.addToPosse("flowchartWindow3", {id:"posse",active:false})

And the result would be that whenever window 1 or 2 was dragged, all of windows 1, 2 and 3 would be dragged. But if window 3 was dragged, it would be dragged alone; windows 1 and 2 would not move.

  • removeFromPosse(elements, posseId)

Remove the given element(s) from Posse(s). As with addToPosse, the second parameter is a varargs parameter: you can supply a number of posse IDs at once:

jsPlumb.removeFromPosse("flowchartWindow1", "posse1", "posse2");
  • removeFromAllPosses

Remove the given element(s) from all of the Posses to which it/they belong.

 jsPlumb.removeFromAllPosses("flowchartWindow1");
 jsPlumb.removeFromAllPosses(["flowchartWindow2", "flowchartWindow3"]);

Since rendering in the Toolkit is handled by the Surface, mapping node/group types to templates and behaviour, the way elements are assigned to a Posse is different to the manual way it is done in the Community Edition. In the Toolkit Edition, assignment to a Posse is handled by an optional assignPosse function passed to a render call:

var surface = toolkitInstance.render({
   ...
   assignPosse:function(obj:Node|Group) {
       //
       // return null, or a string, or a [ name, isActive ] array
       // 
   },
   ...
})

The assignPosse function is defined as:

type PosseAssigner = (obj:Node|Group):string | [string, boolean] | null

It is applied to both Nodes and Groups, and takes as argument the Node/Group. Its return value can be one of 3 things:

  • null, indicating the Node/Group does not belong to a Posse
  • a string, providing the name of the Posse to which the Node/Group should belong
  • an array consisting of [ "posseName", isActive ], providing both the name of the Posse to which the Node/Group should belong and also whether or not it is an active member of the Posse. The default behaviour is for all members to be active.