6 * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
9 * This file contains the base functionality for DOM type adapters.
11 * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
14 * http://github.com/sporritt/jsplumb
15 * http://code.google.com/p/jsplumb
17 * Dual licensed under the MIT and GPL2 licenses.
21 var canvasAvailable = !!document.createElement('canvas').getContext,
22 svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"),
23 // http://stackoverflow.com/questions/654112/how-do-you-detect-support-for-vml-or-svg-in-a-browser
24 vmlAvailable = function() {
25 if (vmlAvailable.vml === undefined) {
26 var a = document.body.appendChild(document.createElement('div'));
27 a.innerHTML = '<v:shape id="vml_flag1" adj="1" />';
29 if (b != null && b.style != null) {
30 b.style.behavior = "url(#default#VML)";
31 vmlAvailable.vml = b ? typeof b.adj == "object": true;
34 vmlAvailable.vml = false;
35 a.parentNode.removeChild(a);
37 return vmlAvailable.vml;
41 Manages dragging for some instance of jsPlumb.
43 var DragManager = function(_currentInstance) {
44 var _draggables = {}, _dlist = [], _delements = {}, _elementsWithEndpoints = {},
45 // elementids mapped to the draggable to which they belong.
46 _draggablesForElements = {};
49 register some element as draggable. right now the drag init stuff is done elsewhere, and it is
50 possible that will continue to be the case.
52 this.register = function(el) {
53 var jpcl = jsPlumb.CurrentLibrary,
54 _el = jpcl.getElementObject(el),
55 id = _currentInstance.getId(el),
56 parentOffset = jpcl.getOffset(_el);
58 if (!_draggables[id]) {
64 // look for child elements that have endpoints and register them against this draggable.
65 var _oneLevel = function(p, startOffset) {
67 for (var i = 0; i < p.childNodes.length; i++) {
68 if (p.childNodes[i].nodeType != 3 && p.childNodes[i].nodeType != 8) {
69 var cEl = jpcl.getElementObject(p.childNodes[i]),
70 cid = _currentInstance.getId(p.childNodes[i], null, true);
71 if (cid && _elementsWithEndpoints[cid] && _elementsWithEndpoints[cid] > 0) {
72 var cOff = jpcl.getOffset(cEl);
73 _delements[id][cid] = {
76 left:cOff.left - parentOffset.left,
77 top:cOff.top - parentOffset.top
80 _draggablesForElements[cid] = id;
82 _oneLevel(p.childNodes[i]);
91 // refresh the offsets for child elements of this element.
92 this.updateOffsets = function(elId) {
93 var jpcl = jsPlumb.CurrentLibrary,
94 el = jpcl.getElementObject(elId),
95 domEl = jpcl.getDOMElement(el),
96 id = _currentInstance.getId(domEl),
97 children = _delements[id],
98 parentOffset = jpcl.getOffset(el);
101 for (var i in children) {
102 var cel = jpcl.getElementObject(i),
103 cOff = jpcl.getOffset(cel);
105 _delements[id][i] = {
108 left:cOff.left - parentOffset.left,
109 top:cOff.top - parentOffset.top
112 _draggablesForElements[i] = id;
118 notification that an endpoint was added to the given el. we go up from that el's parent
119 node, looking for a parent that has been registered as a draggable. if we find one, we add this
120 el to that parent's list of elements to update on drag (if it is not there already)
122 this.endpointAdded = function(el) {
123 var jpcl = jsPlumb.CurrentLibrary, b = document.body, id = _currentInstance.getId(el),
124 c = jpcl.getElementObject(el),
125 cLoc = jsPlumb.CurrentLibrary.getOffset(c),
126 p = el.parentNode, done = p == b;
128 _elementsWithEndpoints[id] = _elementsWithEndpoints[id] ? _elementsWithEndpoints[id] + 1 : 1;
130 while (p != null && p != b) {
131 var pid = _currentInstance.getId(p, null, true);
132 if (pid && _draggables[pid]) {
133 var idx = -1, pEl = jpcl.getElementObject(p), pLoc = jpcl.getOffset(pEl);
135 if (_delements[pid][id] == null) {
136 _delements[pid][id] = {
139 left:cLoc.left - pLoc.left,
140 top:cLoc.top - pLoc.top
143 _draggablesForElements[id] = pid;
151 this.endpointDeleted = function(endpoint) {
152 if (_elementsWithEndpoints[endpoint.elementId]) {
153 _elementsWithEndpoints[endpoint.elementId]--;
154 if (_elementsWithEndpoints[endpoint.elementId] <= 0) {
155 for (var i in _delements) {
157 delete _delements[i][endpoint.elementId];
158 delete _draggablesForElements[endpoint.elementId];
165 this.changeId = function(oldId, newId) {
166 _delements[newId] = _delements[oldId];
167 _delements[oldId] = {};
168 _draggablesForElements[newId] = _draggablesForElements[oldId];
169 _draggablesForElements[oldId] = null;
172 this.getElementsForDraggable = function(id) {
173 return _delements[id];
176 this.elementRemoved = function(elementId) {
177 var elId = _draggablesForElements[elementId];
179 delete _delements[elId][elementId];
180 delete _draggablesForElements[elementId];
184 this.reset = function() {
188 _elementsWithEndpoints = {};
192 // notification drag ended. from 1.5.5 we check automatically if need to update some
193 // ancestor's offsets.
195 this.dragEnded = function(el) {
196 var id = _currentInstance.getId(el),
197 ancestor = _draggablesForElements[id];
199 if (ancestor) this.updateOffsets(ancestor);
202 this.setParent = function(el, elId, p, pId) {
203 var current = _draggablesForElements[elId];
205 if (!_delements[pId])
206 _delements[pId] = {};
207 _delements[pId][elId] = _delements[current][elId];
208 delete _delements[current][elId];
209 var pLoc = jsPlumb.CurrentLibrary.getOffset(p),
210 cLoc = jsPlumb.CurrentLibrary.getOffset(el);
211 _delements[pId][elId].offset = {
212 left:cLoc.left - pLoc.left,
213 top:cLoc.top - pLoc.top
215 _draggablesForElements[elId] = pId;
221 // for those browsers that dont have it. they still don't have it! but at least they won't crash.
223 window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} };
225 window.jsPlumbAdapter = {
229 getAttribute:function(el, attName) {
230 return el.getAttribute(attName);
233 setAttribute:function(el, a, v) {
234 el.setAttribute(a, v);
237 appendToRoot : function(node) {
238 document.body.appendChild(node);
240 getRenderModes : function() {
241 return [ "canvas", "svg", "vml" ];
243 isRenderModeAvailable : function(m) {
245 "canvas":canvasAvailable,
250 getDragManager : function(_jsPlumb) {
251 return new DragManager(_jsPlumb);
253 setRenderMode : function(mode) {
257 mode = mode.toLowerCase();
259 var canvasAvailable = this.isRenderModeAvailable("canvas"),
260 svgAvailable = this.isRenderModeAvailable("svg"),
261 vmlAvailable = this.isRenderModeAvailable("vml");
263 // now test we actually have the capability to do this.
264 if (mode === "svg") {
265 if (svgAvailable) renderMode = "svg";
266 else if (canvasAvailable) renderMode = "canvas";
267 else if (vmlAvailable) renderMode = "vml";
269 else if (mode === "canvas" && canvasAvailable) renderMode = "canvas";
270 else if (vmlAvailable) renderMode = "vml";
282 add: function( elem, classNames ) {
283 jQuery.each((classNames || "").split(/\s+/), function(i, className){
284 if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
285 elem.className += (elem.className ? " " : "") + className;
294 elem.className = classNames !== undefined ?
295 jQuery.grep(elem.className.split(/\s+/), function(className){
296 return !jQuery.className.has( classNames, className );