Required Imports and Basic Setup
I strongly encourage you to read this entire page. There are a few things you need to know about integrating jsPlumb with your UI.
Browser Compatibility
jsPlumb runs on everything from IE6 up. There are some caveats, though, because of various browser/library bugs:
- jQuery 1.6.x and 1.7.x have a bug in their SVG implementation for IE9 that causes hover events to not get fired.
- jQuery 2.0 does not support 6,7 or 8.
- Safari 5.1 has an SVG bug that prevents mouse events from being passed through the transparent area of an SVG element (Safari 6.x does not seem to have the same problem)
Setup
Required Imports
jQuery
- jQuery 1.3.x or higher. jsPlumb has been tested with everything up to 2.x.
- jQuery UI 1.7.x or higher (if you wish to support drag and drop). Always remember to check that the versions of jQuery and jQuery UI you are using are compatible.
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js"></script> <script type="text/javascript" src="PATH_TO/jquery.jsPlumb-1.5.0-all-min.js "></script>
Touch Events
jQuery <= 1.9.x does not support touch events, but there is a neat plugin you can use for this - jQuery UI Touch Punch. You need to include it in your page after both jQuery and jQueryUI. But note that it doesn’t work properly with the makeSource
method…better to use vanilla jsPlumb!
Initializing jsPlumb
You should not start making calls to jsPlumb until the DOM has been initialized - perhaps no surprises there. To handle this, you should bind to the ready
event on jsPlumb (or the instance of jsPlumb you are working with):
jsPlumb.bind("ready", function() {
...
// your jsPlumb related init code goes here
...
});
There’s a helper method that can save you a few precious characters:
jsPlumb.ready(function() {
...
// your jsPlumb related init code goes here
...
});
If you bind to the ready
event after jsPlumb has already been initialized, your callback will be executed immediately.
Multiple jsPlumb instances
jsPlumb is registered on the browser’s Window by default, providing one static instance for the whole page to use. Should you need to, though, you can instantiate independent instances of jsPlumb, using the getInstance
method, for example:
var firstInstance = jsPlumb.getInstance();
The variable firstInstance
can now be treated exactly as you would treat the jsPlumb
variable - you can set defaults, call the connect
method, whatever:
firstInstance.importDefaults({
Connector : [ "Bezier", { curviness: 150 } ],
Anchors : [ "TopCenter", "BottomCenter" ]
});
firstInstance.connect({
source:"element1",
target:"element2",
scope:"someScope"
});
getInstance
optionally takes an object that provides the defaults:
var secondInstance = jsPlumb.getInstance({
PaintStyle:{
lineWidth:6,
strokeStyle:"#567567",
outlineColor:"black",
outlineWidth:1
},
Connector:[ "Bezier", { curviness: 30 } ],
Endpoint:[ "Dot", { radius:5 } ],
EndpointStyle : { fillStyle: "#567567" },
Anchor : [ 0.5, 0.5, 1, 1 ]
});
secondInstance.connect({ source:"element4", target:"element3", scope:"someScope" });
Z-index Considerations
You need to pay attention to the z-indices of the various parts of your UI when using jsPlumb, in particular to ensure that the elements that jsPlumb adds to the DOM do not overlay other parts of the interface.
jsPlumb adds an element to the DOM for each Endpoint, Connector and Overlay. So for a connection having visible Endpoints at each end and a label in the middle, jsPlumb adds four elements to the DOM. The actual elements it adds depend on the renderer in use (Canvas/SVG/VML).
To help you organise z-indices correctly, jsPlumb adds a CSS class to each type of element it adds. They are as follows:
Component | Class |
Endpoint | _jsPlumb_endpoint |
Connector | _jsPlumb_connector |
Overlay | _jsPlumb_overlay |
In addition, whenever the mouse is hovering over an Endpoint or Connection, that component is assigned the class _jsPlumb_hover
. For more information about styling jsPlumb with CSS, see this page.
Where does jsPlumb add elements?
It’s important to understand where in the DOM jsPlumb will add any elements it creates, as it has a bearing on the markup you can use with jsPlumb.
Early versions of jsPlumb added everything to the end of the body element. This has the advantage of being the most flexible arrangement in terms of supporting which elements can be connected, but in certain use cases produced unexpected results. Consider the arrangement where you have some connected elements in a tab: you would expect jsPlumb to add elements inside the tab, so that when the user switches tabs and the current one is hidden, all the jsPlumb stuff is hidden too. But when the elements are on the body, this does not happen!
For this reason, from 1.3.0 onwards, jsPlumb’s default behaviour is to attach Endpoint elements to the parent of the element the Endpoint is attached to (not the actual element the Endpoint is attached to), and to attach Connector elements to the parent of the source Endpoint in the Connection. This results in all the elements of the Connection being siblings inside the same parent node, which jsPlumb requires, for various reasons arising from the trade off between doing lots of maths and the performance of the whole thing.
In general, the default behaviour means that you cannot do things like this:
<body>
<div id="container0">
<div id="node0></div>
</div>
<div id="container1">
<div id="node1"></div>
</div>
</body>
<script type="text/javascript">
var e0 = jsPlumb.addEndpoint("node0"),
e1 = jsPlumb.addEndpoint("node1");
jsPlumb.connect({ source:e0, target:e1 });
</script>
…because the elements created for e0
and e1
would have different parent nodes, and an attempt to make a Connection between them would not work. For the example just given, you would need to register the Endpoints on the container
divs (and then quite possibly throw away the node
divs):
<body>
<div id="container0">
</div>
<div id="container1">
</div>
<body>
<script type="text/javascript">
var e0 = jsPlumb.addEndpoint("container0"),
e1 = jsPlumb.addEndpoint("container1");
jsPlumb.connect({ source:e0, target:e1 });
</script>
now ‘e0’ and ‘e1’ have the same parent - the body element.
Remember that you can use the anchor
parameter to an addEndpoint
call to specify where on an element you want an Endpoint to appear. Used in conjunction with CSS classes (discussed below), you can have an Endpoint on top of some element wherever you like.
Note regarding the drawEndpoints option on jsPlumb.connect: with the default behaviour, jsPlumb uses the offsetParent of the source endpoint in a connection to make final adjustments to the position of a connector. When drawEndpoints is set to false, there is no offsetParent of the source endpoint because it is not visible. If your connection lies inside some container other than the document body, the connector will not be able to take that container’s offset into account, and will most likely not be in the right place. You should either use the Blank
endpoint when you don’t want to see one, or instruct jsPlumb to attach everything to the document body (see below).
Overriding the default behaviour
From version 1.3.3, the Container
concept was reintroduced into jsPlumb, because there are use cases in which the default behaviour makes it difficult or impossible to build the UI you want. You can instruct jsPlumb to use some element as the parent of everything jsPlumb adds to the UI through usage of the jsPlumb.Defaults.Container
property, or you can set a container
parameter on calls to addEndpoint
or connect
(if this is set and a default is also set, this default value is actually used. This may seem counter-intuitive, and could of course be changed if there is feedback from the community that this would be a good idea).
Some examples:
- Set a container to use as the default container, using a jQuery selector (you can supply a selector, element id, or Element here), and then add an Endpoint. The canvas (or SVG/VML element) created will be a child of the document body:
jsPlumb.setContainer($("body"));
...
jsPlumb.addEndpoint(someDiv, { endpoint options });
- Set a container to use as the default container, using an element id, and then connect two elements. The elements created in this example will be children of the element with id “containerId”:
jsPlumb.Defaults.Container = "containerId";
...
jsPlumb.connect({ source:someDiv, target:someOtherDiv });
- Pass a container to use into a
jsPlumb.connect
call, using a selector. The elements created in this example will be children of the element with id “containerId”:
jsPlumb.connect({ source:someDiv, target:someOtherDiv, container:$("#containerId") });
- Pass a container to use into a
jsPlumb.addEndpoint
call, using an element id. The element created in this example will be a child of the element with id “containerId”:
jsPlumb.addEndpoint(someDiv, { ..., container:"containerId" });
Element Dragging
A common feature of interfaces using jsPlumb is that the elements are draggable. Unless you absolutely need to configure this behaviour using your library’s underlying method, you should use jsPlumb.draggable
:
jsPlumb.draggable("elementId");
You can also pass a selector from your library in to this method:
jsPlumb.draggable($(".someClass"));
This method is just a wrapper for the underlying library’s draggable functionality, and there is a two-argument version in which the second argument is passed down to the underlying library (example below).
Drag Containment
A common request is for the ability to contain the area within which an element may be dragged. Remember that jsPlumb’s draggable
method is just a pass-through to the underlying library’s drag functionality, so for jQuery this is as simple as providing a containment
parameter:
jsPlumb.draggable($("someSelector"), {
containment:"parent"
});
Right now in vanilla jsPlumb you can use this syntax to constrain dragging to a parent element:
jsPlumb.draggable(".someSelector", {
constrain:true
});
Dragging nested elements
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 recalculateOffsets
function.
This does not apply, though, if you are dragging nested children. In that case, jsPlumb automatically updates the child offsets after draggings ends - if you used jsPlumb.draggable
to configure the child elements as draggable.
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>
…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");
...
$("#container .header).hide(); // hide the header bar. this will alter the offset of the other child elements...
jsPlumb.recalculateOffsets("container"); // tell jsPlumb that the internal dimensions have changed.
// you can also use a selector, eg $("#container")
Another setup we have seen in which you will need to call recalculateOffsets
is when you have some draggable parent that itself contains draggable child nodes:
<div class="parent">
<div class="child">child 1</div>
<div class="child">child 2</div>
</div>
If you make both .parent
and .child
draggable, you need to make a call to recalculateOffsets
whenever a .child
node stops dragging:
jsPlumb.draggable($(".parent"));
jsPlumb.draggable($(".child"), {
containment:"parent",
stop:function() {
jsPlumb.recalculateOffsets($(this).parent());
}
});
doWhileSuspended
This function abstracts out the pattern of suspending drawing, doing something, and then re-enabling drawing:
jsPlumb.doWhileSuspended(fn, [doNotRepaintAfterwards]);
Example usage:
jsPlumb.doWhileSuspended(function() {
// import a billion things here
});
By default, this will run a repaint at the end. But you can suppress that, should you want to:
jsPlumb.doWhileSuspended(function() {
// import a billion things here
}, true);
Note just in case you take it literally, you should probably not attempt to import a billion things into jsPlumb. You may find that performance suffers a little.
Anchor Types
Continuous anchors are the type that require the most maths, because they have to calculate their position every time a paint cycle occurs.
Dynamic and Perimeter anchors are the next slowest (with Perimeter being slower than most Dynamic Anchors as they are actually Dynamic Anchors that have, by default, 60 locations to choose from).
Static anchors like Top
, Bottom
etc are the fastest.